概述
什么叫反射?
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种运行时动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。
反射可以“看透”程序的运行情况,可以让我们在运行期知晓一个类或者实例的运行状况,可以动态地加载和调用,虽然有一定的性能忧患,但它带给我们的便利远远大于其性能缺陷。
实现反射的几个步骤:
- 获取 Class 类
- 通过 Class 创建对象
- 获取类中的方法
- 获取类中的属性、属性值或者类型
反射相关的类
要实现反射机制一般要使用下面四个类:
- Class
- Constructor
- Method
- Field
Class
理解 Class 对象:
https://blog.csdn.net/javazejian/article/details/70768369?utm_source=gold_browser_extension
https://www.cnblogs.com/yaoyinglong/p/Class%E5%AF%B9%E8%B1%A1.html
Java 语言是先把 Java 源文件编译成后缀为 class 的字节码文件,然后再通过 ClassLoader 机制把这些类文件加载到内存中,最后生成实例来执行的,这是 Java 处理的基本机制,但是加载到内存中的数据是如何描述一个类的呢?比如 Dog.class 文件中定义的是一个 Dog 类,那么它在内存中是如何展现的呢?
Java 使用一个元类(MetaClass)来描述加载到内存中的类数据,这就是 Class 类,它是一个描述类的类对象,比如 Dog.classs 文件加载到内存中后就会一个Class 的实例对象来描述它。因为Class类是“类中类”,也就预示着它有很多特殊的地方:
- 无构造函数:Java 中的类一般都有构造函数,用于创建实例对象。但是 Class 类却没有构造函数,不能实例化,Class 对象是在加载类时由 Java 虚拟机通过调用类的加载器中的defineClass方法自动构造的。
- 可以描述基本类型:虽然8个基本类型在 JVM 中并不是一个对象,它们一般存在于栈内存中,但是 Class 类仍然可以描述它们。例如:可以使用 int.class 标识int类型的类对象。
- 其对象都是单例模式:一个Class的实例对象描述一个类,并且只描述一个类,反过来也成立,一个类只有一个Class实例对象。
下面的代码都是返回true的:
1 | // 类的属性class所引用的对象与实例对象的getClass返回值相同 |
Class 类是 Java 反射的入口,只有在获得了一个类的描述对象后才能动态地加载、调用。
下面介绍一下获取 Class 的三种方法:
第一种方式:通过对象的 getClass 方法获取:
1 | OuterClass outerClass = new OuterClass(); |
第二种方式:直接通过类名获取,也就是通过类属性的方式:
1 | Class cls = OuterClass.class; |
第三种方式:通过类的全称获取,使用 forName 方法加载:
1 | try { |
获得了 Class 对象后,就可以通过下面Class 的几个方法来获取构造方法、属性、方法以及注解,为后续的反射代码铺平了道路:
- getConstructors()/getDeclaredConstructors():获取类中所有的构造方法
- getFields()/getDeclaredFields():获取类中所有的属性
- getMethods()/getDeclaredMethods():获取类中所有的方法
- getAnnotations()/getDeclaredAnnotationsv():获取类所有的注解
- getSuperclass():获取直接继承的父类
- getGenericSuperclass():获取直接继承的父类(包含泛型参数)
get*
和 getDeclared*
方法的区别
Java Class 类提供了很多的 get*
和 getDeclared*
方法,比如 getDeclaredMethods() 和 getMethods(),那么它们之间有什么差别呢?
getMethods() 方法获取的是所有的 public 访问级别的方法,包括从父类继承的方法。
getDeclaredMethods() 方法获得的是自身类的所有方法,包括公用方法、私有方法等,而且不受限于访问权限。但不包括继承的方法。
getDeclaredField(s)和getField(s)同上。
Java 之所以如此处理,是因为反射本身只是正常代码逻辑的一种补充,而不是让正常代码逻辑产生翻天覆地的变动,所以public得属性和方法最容易获取,私有属性和方法也可以获取,但要先定本类。
Method
Method 的几个方法:
- getModifiers():获取方法修饰符,比如Modifier.PUBLIC、Modifier.STATIC等
- getDeclaringClass():返回表示声明由此Method对象表示的方法的类的Class对象
Field
Field 的几个方法:
- getDeclaringClass():返回表示声明由Field对象表示的字段的类或接口的 Class 对象。比如 mName 是 Student 类的对象,那么这个方法就返回 Student 对应的 Class 对象。
- getClass():返回 Field Class 对象。
- getType():返回一个Class对象,用于标识Field对象所表示的字段的声明类型。比如 mName 是String 类型的,那么就返回 String 对应的 Class 对象。
实例化对象
实例化对象的方法:
- Class.newInstance():只能够调用无参的构造函数,即默认的构造函数。要求被调用的构造函数是可见的,也即必须是public类型的。
- Constructor.newInstance():可以根据传入的参数,调用任意构造构造函数。可以调用私有的构造函数,需要通过setAccessible(true)实现。
通过 Class.newInstance 方法实例化对象:
1 | try { |
通过 Constructor.newInstance() 方法实例化对象:
1 | try { |
关于 setAccessible
我们平时在反射代码时,尤其时反射调用私有的 Field 或者 Method 时,会设置 setAccessible(true)
,如果不设置,就会抛出异常:
1 | java.lang.IllegalAccessException: Class java.lang.Class<com.example.heqiang.testsomething.commontest.OtherTestActivity> cannot access private field boolean com.example.heqiang.testsomething.reflect.TestA.mTestBoolean of class java.lang.Class<com.example.heqiang.testsomething.reflect.TestA> |
那么可能大家就会认为 Accessible 代表的是访问权限的控制。其实并不是这样,我们可以访问一个公有public的属性或者方法,不去设置 setAccessible(true)
,此时是可以访问或者执行的。但是我们打印下面的代码:
1 | field.isAccessible() |
发现返回的是 false。
为什么返回了 false 还能执行呢?这也说明,属性和方法对象的Accessible属性并不是用来决定是否可以访问的。
Accessible 其实指的是是否更容易获得,是否进行安全检查。
我们知道,动态修改一个类或者方法或者执行方法时都会受到Java安全体系的制约,而安全的处理是非常消耗资源的(性能非常低),因此对于运行期要执行的方法或要修改的属性就提供了 Accessible 可选项:由开发者决定是否要逃避安全体系的检查。
查看源码,我们发现 Field 、Method 和 Constructor 都是继承自 AccessibleObject 的,isAccessible() 和 setAccessible 都是 AccessibleObject 的方法。
1 | public class AccessibleObject implements AnnotatedElement { |
设置 setAccessible 其实就是设置 override 属性。
再来看一下 Method 的 invoke 方法:
1 | public Object invoke(Object obj, Object... args) |
如果 override 为 true,那么就不需要执行安全检查,这就可以大幅度地提升了系统性能(当然,由于取消了安全检查,也就可以允许private方法,访问private私有属性了)。经过有人测试,在大量的反射情况下,设置 Accessible 为true可以提升性能 20 倍以上。
因此,我们在进行反射编程时,设置 Accessible 为true,不仅仅是因为操作习惯的问题,这是在为我们的系统性能考虑。
是否需要太关注反射效率
我们平时编程,总会有人经常说:反射的效率是非常低的,不到万不得已就不要使用。事实上,这句话的前半句是对的,后半句是错的。
反射的效率相对于正常的代码执行确实效率低很多(有人经过测试,相差15倍左右),但它是一个非常有效的运行期工具类,只要代码结构清晰、可读性好那就先开发起来,等到性能测试时证明此处性能确实有问题再修改也不迟(一般情况下反射并不是性能的终极杀手,而代码结构混乱、可读性差则很可能埋下性能隐患)。很少有项目是因为反射问题引起系统效率故障的。而根据二八原则,80%的性能消耗在20%的代码上,这20%的代码才是我们关注的重点,不要单单把反射作为重点关注对象。
反射和private属性
我们经常会被问到这样的问题:我们通过反射恶意访问private成员和方法,既然能访问为什么要private,它的使用意义是什么?
首先,private的属性和方法是可以通过反射来访问的。private 并不是来解决安全问题的,它只是OOP(面向对象编程)的封装概念,想做到的是不暴露内部的实现。如果你想解决安全的问题,就要想想别的办法了。
如果在特殊的情况下,你需要关注甚至修改内部实现(反射),这种例外的存在并不影响封装的理念。这更多的是一种“知道这么多就够了”,而不是“这是机密,坚决不能让外人知道”。
setAccessible也是一种hack,它可以取消安全检查,它的意义是:你清楚内部实现,你知道你在做什么,相信你不会搞砸。
反射和final属性
关于反射修改final变量的问题,网上有文章说不能按照修改普通变量的方式来修改,要修改 Filed 的 modifiers 属性,把final属性去掉后才能修改,对此我做过测试,不修改 modifiers 属性也是可以对final变量进行修改的,但是和普通的变量修改也有区别:就是不管final是不是public的都要调用 setAccessible(true)
,否则还是报错。
我是在 Android 平台上测试的,可能是底层虚拟机的差异导致的,先记录于此,后面有时间再详细了解。
反射实现代码
测试类
TestA
1 | public class TestA { |
TestB
1 | public class TestB { |
OutterClass
1 | public class OuterClass { |
获取属性值
1 | public void getFiled(){ |
修改属性值
1 | public void setField(){ |
调用方法
1 | public void invokeMethod(){ |
调用带参数方法并获取返回值
1 | public void invokeMethodWithArgs(){ |
调用私有属性的方法
1 | public void invokePirvateFieldMethod(){ |
内部类
1 | public void testReflectInnerClass(){ |
获取方法的类型和参数
1 | public class ReflectTest { |
1 | public void testRefect(){ |
结果:
1 | D/Test (23196): ----------------------> |