反射概述
反射概述
- JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
- 对于任意一个对象,都能够调用它的任意一个方法和属性;
- 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
- 要想解剖一个类,必须先要获取到该类的字节码文件对象。
- 而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象
获取class文件对象的三种方式
- 这三种方式为:
- a:Object类的getClass()方法
- b:静态属性class
c:Class类中静态方法forName()
第一种方法【Object类的getClass()方法】
- 1.在内存中新建一个Person的实例,对象p对这个内存地址进行引用
- 2.对象p调用getClass()返回对象p所对应的Class对
- 3.调用newInstance()方法让Class对象在内存中创建对应的实例,并且让p2引用实例的内存地址
1
2
3Person p = new Person();
Class<?> cls=p.getClass();
Person p2=(Person)cls.newInstance();
第二种方法【静态属性class】
- 1.获取指定类型的Class对象,这里是Person
- 2.调用newInstance()方法在让Class对象在内存中创建对应的实例,并且让p引用实例的内存地址
1
2Class<?> cls=Person.Class();
Person p=(Person)cls.newInstance();
第三种方法【Class类中静态方法forName()】
- 1.通过JVM查找并加载指定的类(上面的代码指定加载了com.fanshe包中的Person类)
- 2.调用newInstance()方法让加载完的类在内存中创建对应的实例,并把实例赋值给p
- 注意:如果找不到时,它会抛出 ClassNotFoundException 这个异常,这个很好理解,因为如果查找的类没有在 JVM 中加载的话,自然要告诉开发者。
1
2Class<?> cls=Class.forName("com.yc.Person"); //forName(包名.类名)
Person p= (Person) cls.newInstance();
通过反射获取无参构造方法并使用
- A:获取所有构造方法
- public Constructor<?>[] getConstructors()
- public Constructor<?>[] getDeclaredConstructors()
- B:获取单个构造方法
- public Constructor
getConstructor(Class<?>… parameterTypes) - public Constructor
getDeclaredConstructor(Class<?>… parameterTypes)
- 方法关键字
- getDeclareMethods() 获取所有的方法
- getReturnType() 获取方法的返回值类型
- getParameterTypes() 获取方法的传入参数类型
- getDeclareMethod(“方法名,参数类型.class,….”) 获得特定的方法
- 构造方法关键字
- getDeclaredConstructors() 获取所有的构造方法
- getDeclaredConstructors(参数类型.class,….) 获取特定的构造方法
- 成员变量
- getDeclaredFields 获取所有成员变量
- getDeclaredField(参数类型.class,….) 获取特定的成员变量
- 父类和父接口
- getSuperclass() 获取某类的父类
- getInterfaces() 获取某类实现的接口
反射的定义
- JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制
反射的组成
- 由于反射最终也必须有类参与,因此反射的组成一般有下面几个方面组成:
- 1.java.lang.Class.java:类对象;
- 2.java.lang.reflect.Constructor.java:类的构造器对象;
- 3.java.lang.reflect.Method.java:类的方法对象;
- 4.java.lang.reflect.Field.java:类的属性对象;
- 反射中类的加载过程
- 根据虚拟机的工作原理,一般情况下,类需要经过:加载->验证->准备->解析->初始化->使用->卸载这个过程,如果需要反射的类没有在内存中,那么首先会经过加载这个过程,并在在内存中生成一个class对象,有了这个class对象的引用,就可以发挥开发者的想象力,做自己想做的事情了。
反射的作用有哪些
- 前面只是说了反射是一种具有与Java类进行动态交互能力的一种机制,在Java和Android开发中,一般情况下下面几种场景会用到反射机制.
- 需要访问隐藏属性或者调用方法改变程序原来的逻辑,这个在开发中很常见的,由于一些原因,系统并没有开放一些接口出来,这个时候利用反射是一个有效的解决方法
- 自定义注解,注解就是在运行时利用反射机制来获取的。
- 在开发中动态加载类,比如在Android中的动态加载解决65k问题等等,模块化和插件化都离不开反射,离开了反射寸步难行。
反射的相关使用
通过反射获取成员变量[包含私有]并使用
1 | clazz = Class.forName("com.ycbjie.ycpaidian.four.ReflexUtils"); |
通过反射获取无参无返回值成员方法[包含私有]并使用
1 | clazz = Class.forName("com.ycbjie.ycpaidian.four.ReflexUtils"); |
通过反射获取带参无返回值成员方法并使用
1 | clazz = Class.forName("com.ycbjie.ycpaidian.four.ReflexUtils"); |
通过反射获取带参带返回值成员方法并使用
1 | clazz = Class.forName("com.ycbjie.ycpaidian.four.ReflexUtils"); |
相关知识点
设置.setAccessible(true)暴力访问权限
- 一般情况下,我们并不能对类的私有字段进行操作,利用反射也不例外,但有的时候,例如要序列化的时候,我们又必须有能力去处理这些字段,这时候,我们就需要调用AccessibleObject上的setAccessible()方法来允许这种访问,而由于反射类中的Field,Method和Constructor继承自AccessibleObject,因此,通过在这些类上调用setAccessible()方法,我们可以实现对这些字段的操作。
1 | Field gradeField = clazz.getDeclaredField("code"); |
获取Filed两个方法的区别
- 两者的区别就是 getDeclaredField() 获取的是 Class 中被 private 修饰的属性。 getField() 方法获取的是非私有属性,并且 getField() 在当前 Class 获取不到时会向祖先类获取。
1 | //获取所有的属性,但不包括从父类继承下来的属性 |
获取Field的类型
- 可以看到 getGenericType() 确实把泛型都打印出来了,它比 getType() 返回的内容更详细。
1 | public Type getGenericType() {} |
Method获取方法名,获取方法参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54//获取方法名
//获取方法参数
//返回的是一个 Parameter 数组,在反射中 Parameter 对象就是用来映射方法中的参数。
public Parameter[] getParameters() {}
//Method获取方法名
Method[] declaredMethods1 = clazz.getDeclaredMethods();
for ( Method m : declaredMethods1 ) {
System.out.println("method name:"+m.getName());
}
//获取方法参数
for ( Method m : declaredMethods1 ) {
System.out.println("获取方法参数method name:"+m.getName());
//获取参数
Parameter[] paras;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
paras = m.getParameters();
for (Parameter c : paras ) {
System.out.println("获取参数parameter :"+c.getName()+" "+c.getType().getName());
}
}
//获取所有的参数类型
Class[] pTypes = m.getParameterTypes();
for ( Class c : pTypes ) {
System.out.print("参数类型method para types:"+ c.getName());
}
System.out.println();
System.out.println("==========================================");
}
//打印日志如下所示:
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: ==========================================
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: 获取方法参数method name:copyText
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: 获取参数parameter :arg0 java.lang.String
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: 参数类型method para types:java.lang.String
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: ==========================================
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: 获取方法参数method name:copyText
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: 获取参数parameter :arg0 java.lang.String
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: 获取参数parameter :arg1 java.lang.String
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: 参数类型method para types:java.lang.String参数类型method para types:java.lang.String
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: ==========================================
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: 获取方法参数method name:getCode
07-31 19:23:13.192 4022-4022/com.ycbjie.ycpaidian I/System.out: ==========================================
07-31 19:23:13.192 4022-4022/com.ycbjie.ycpaidian I/System.out: 获取方法参数method name:getUserInfo
07-31 19:23:13.192 4022-4022/com.ycbjie.ycpaidian I/System.out: 获取参数parameter :arg0 java.lang.String
07-31 19:23:13.192 4022-4022/com.ycbjie.ycpaidian I/System.out: 参数类型method para types:java.lang.String
07-31 19:23:13.192 4022-4022/com.ycbjie.ycpaidian I/System.out: ==========================================
07-31 19:23:13.192 4022-4022/com.ycbjie.ycpaidian I/System.out: 获取方法参数method name:setCode
07-31 19:23:13.192 4022-4022/com.ycbjie.ycpaidian I/System.out: 获取参数parameter :arg0 int
07-31 19:23:13.192 4022-4022/com.ycbjie.ycpaidian I/System.out: 参数类型method para types:int
07-31 19:23:13.192 4022-4022/com.ycbjie.ycpaidian I/System.out: ==========================================
3.1.5 Method方法的invoke()方法执行
- Method 调用 invoke() 的时候,存在许多细节:
- invoke() 方法中第一个参数 Object 实质上是 Method 所依附的 Class 对应的类的实例,如果这个方法是一个静态方法,那么 ojb 为 null,后面的可变参数 Object 对应的自然就是参数。
- invoke() 返回的对象是 Object,所以实际上执行的时候要进行强制转换。
- 在对Method调用invoke()的时候,如果方法本身会抛出异常,那么这个异常就会经过包装,由Method统一抛InvocationTargetException。而通过InvocationTargetException.getCause() 可以获取真正的异常。