1. 对反射的理解
Java Reflection API : Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
反射是运行中的程序检查自己和软件运行环境的能力,它可以根据它发现的进行改变。通俗的讲就是反射可以在运行时根据指定的类名获得类的信息。
通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。
程序中一般的对象的类型都是在编译期就确定下来的,而Java反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。
反射的核心是JVM在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。
2. 反射可以做什么
- 开发各种通用框架:很多框架(比如Spring)都是配置化的(比如通过XML文件配置JavaBean,Action之类的),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射——运行时动态加载需要加载的对象。比如Spring、Struts等。
- a. 与注解相结合的框架 例如Retrofit
- b. 单纯的反射机制应用框架 例如EventBus 2.x
- c. 动态生成类框架 例如Gson
逆向代码 ,例如反编译
当我们在使用IDE(如Eclipse,IDEA)时,当我们输入一个对象或类并想调用它的属性或方法时,一按点号,编译器就会自动列出它的属性或方法,这里就会用到反射。
3. 反射的优缺点
优点
- 运行期类型的判断,动态类加载,动态代理使用反射。动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中,它的灵活性就表现的十分明显。
缺点
- 性能是一个问题,反射相当于一系列解释操作,通知jvm要做的事情,性能比直接的java代码要慢很多。这类操作总是慢于只直接执行相同的操作。
4. 反射的使用
4.1 Class对象
我们用到反射这个知识点,肯定是想要在运行时得到类的信息,根据类的那些信息去做一些特定的操作。那么,首先无疑就是得到类的信息,在JDK中提供了Class对象来保存类的信息。所以,反射的第一步就是得到Class对象
三种方法
- 通过对象getClass方法
Person person = new Person();
Class<?> class1 = person.getClass();
- 通过类的class属性
Person person = new Person();
Class<?> class1 = Person.class;
- 通过Class类的静态方法——forName()来实现
限制条件
- 第一,forName中的字符串必须是全限定名
- 第二,这个Class类必须在classpath的路径下面,因为该方法会抛出
ClassNotFoundException
的异常
try {
class1 = Class.forName("com.reflectdemo.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
获得Class对象后。可通过Class类的成员方法获取Class对象的其他信息,比如:得到类的名字、类的class对象的属性、方法、构造函数等
4.2 构造器
利用Java反射可以得到一个类的构造器,并根据构造器,在运行时动态的创建一个对象
- 获取构造器 —— 方式1
Constructor[] constructors = exampleObjectClass.getConstructors();
for (Constructor constructor : constructors){
System.out.println(constructor.toString());
}
输出
public cn.byhieg.reflectiontutorial.ExampleObject(int,java.lang.Integer)
public cn.byhieg.reflectiontutorial.ExampleObject(java.lang.String)
public cn.byhieg.reflectiontutorial.ExampleObject()
- 获取构造器 —— 方式2(已知构造方法的参数类型)
Constructor constructor = exampleObjectClass.getConstructor(String.class);
System.out.println(constructor.toString());
输出
public cn.byhieg.reflectiontutorial.ExampleObject(java.lang.String)
- 获取另一个构造器
Constructor constructor = exampleObjectClass.getConstructor(int.class,Integer.class);
System.out.println(constructor.toString());
- 获取每个构造器对应的参数
Constructor[] constructors = exampleObjectClass.getConstructors();
for (Constructor constructor : constructors){
Class[] parameterTypes = constructor.getParameterTypes();
System.out.println("构造器参数如下========================");
for (Class clz : parameterTypes){
System.out.println("参数类型 " + clz.toString());
}
}
输出
构造器参数如下========================
参数类型 class java.lang.String
构造器参数如下========================
参数类型 int
参数类型 class java.lang.Integer
这里,可以看出无参构造方法,是不打印出结果的。
- 根据构造器的各种信息,动态创建一个对象
Object object = constructor.newInstance(1,100);
System.out.println(object.toString());
这种创建对象的方式既可以通过有参构造器创建也可以通过无参构造器创建;同时构造器对象获取方式为getConstructor
或者getConstructors
4.3 变量
- 同构造器,有
getFields()
和getFieldString name)
,一个获取到Fields[]数组,一个指定参数名获取对应的变量 - 同时Field类还提供了设置变量值的方法
public void set(Object obj, Object value)
4.4 方法
反射可以让我们得到方法名,方法的参数,方法的返回类型,以及调用方法等功能。
-
getMethods()
获取所有方法 -
getMethod(String name, Class<?>... parameterTypes)
根据参数,得到具体的方法 - Method提供了
getParameterTypes()
获取传入参数的方法 - Java反射支持通过invoke调用得到的方法:
Object invoke(Object obj, Object... args)
4.5 私有变量与私有方法
- 上面的方法只能得到public方法和变量,无法得到非public修饰的方法和变量,Java提供了额外的方法来得到非public变量与方法。
- 即通过
getDeclaredFields
与getDeclaredMethods
方法得到私有的变量与方法,同样也支持用getDeclaredField(变量名)
与getDeclaredMethod(方法名)
的形式得到指定的变量名与方法名。 - 但是这样得到的Field对象与Method对象无法直接运用,必须让这些对象调用
setAccessible(true)
,才能正常运用。之后的方式就可上面讲的一样了。
4.6 注解
- Java给我们提供了在运行时获取类的注解信息,可以得到类注解,方法注解,参数注解,变量注解。
- 与上面获取方式一样,Java提供了2种获取方式,一种是获取全部的注解,返回一个数组,第二种是指定得到指定的注解。
- 类注解使用Class对象调用
getAnnotations
得到的,方法注解和变量注解是一样的,分别用method对象与field对象调用getDeclaredAnnotations
得到注解
4.7 泛型
步骤
- 反射得到返回类型为泛型类的方法
- 调用
getGenericReturnType
得到方法返回类型中的参数化类型 - 判断该type对象能不能向下转型为
ParameterizedType
- 转型成功,调用
getActualTypeArguments
得到参数化类型的数组,因为有的泛型类,不只只有一个参数化类型如Map - 取出数组中的每一个的值,转型为Class对象输出。
代码
Class clz = GenericObject.class;
Method method = clz.getMethod("getLists");
Type genericType = method.getGenericReturnType();
if(genericType instanceof ParameterizedType){
ParameterizedType parameterizedType = ((ParameterizedType) genericType);
Type[] types = parameterizedType.getActualTypeArguments();
for (Type type : types){
Class actualClz = ((Class) type);
System.out.println("参数化类型为 : " + actualClz);
}
}
4.8 数组
- Java反射可以对数组进行操作,包括创建一个数组,访问数组中的值,以及得到一个数组的Class对象。
- 对于得到一个数组的Class对象,简单的可以用
int[].class
,或者利用Class.forName
的形式得到