文章首发至个人公众号:追风栈Binary,欢迎批评指正
反射的字面含义,除了物理上的意义外,一般理解就是某个事物所反映出的内在性质。Java中也存在这种反射机制,
Wiki
中对于Java反射的定义指的是:在程序运行期间可以访问、检测和修改对象本身状态和行为的能力。这种解释会在后面进行通俗化解释。除了面向对象,Java反射也可以说是Java的核心理念。在大型的业务代码中和Github
上开源的优秀框架代码中,都可以看到Java反射机制的影子。
首先强推一下JetBrains
公司的Java IDE神器IntelliJ IDEA
,从第一次接触到现在的日常使用中这两年多,越来越觉得这款IDE
是一种无与伦比的存在。在IDEA
之前是Eclipse
,而如今:
什么是Java反射?
不通俗的讲,Java反射指的是程序代码在运行的过程中,对于程序中任意的类或者对象,都可以在运行时获取得到它们的属性或者方法,包括私有方法和私有变量。通俗的讲,举个例子,你的程序中有100个类,每个类中都均有100个成员变量和100个方法。但我只需要知道每个类的名字,我就可以通过一种机制在程序运行时把这些100个类中的所有成员变量和方法都获取,并且还可以加以调用。这种机制就是Java反射(Java Reflect)
。画了幅图来展示这段话的含义。
为什么反射可以做到这些?
听上去Java反射就像是一个魔法,那么到底是谁赋予了它这种能力?这里就不得不引出另一个Java的内容:Java中的Class。这个Class
如影随形的伴随着反射的过程,没有它,反射也立刻失去魔法效力。
我们日常写的Java Class文件,都是对一类通用共性事物的语言描述,然后通过这个类去生成我们所需要的对象。那么问题来了,我们写的Class类是不是对象?是的,我们平时写的Java业务类也都是一个对象,它是java.lang.Class
的对象,这意味着每一个类可以生成自己的对象之外,自身也还是Class的对象。可以以一种上帝视角来看待普通的Java类,那么我们就是那个上帝Class,那些产生其它具体对象的类,就是我们这个上帝Class的对象。查看这个Class的源码,来窥探其中的一些秘密。
/* java.lang.Class源码,私有方法,只有JVM才可以创建Class的对象
* Private constructor. Only the Java Virtual Machine creates Class objects.
* This constructor is not used and prevents the default constructor being
* generated.
*/
private Class(ClassLoader loader, Class<?> arrayComponentType) {
// Initialize final field for classLoader. The initialization value of non-null
// prevents future JIT optimizations from assuming this final field is null.
classLoader = loader;
componentType = arrayComponentType;
}
可以看出这个Class类的构造函数被private
修饰,是一个私有方法,并且注释中也解释了说这个方法只能由JVM
来调用创建Class的对象。这意味着如下的语句是错误的,输入如下语句会直接报错。
虽然我们无法直接创建上帝这个自身Class的直接对象,但是可以通过其它的方式来创建,并且在后面可以看到反射中提及的方法,都是在java.lang.Class
这个类中定义的。
Java反射的第一步
我们执行反射的目的无非只有两个:获取成员变量或者获取方法。Java反射也是基于这两个目标来进行反射的,但在反射之前,首先需要解决上面留下的问题,怎么获取Class的对象?这里先假定我们定义了一个User
类来讨论这个问题。
有三种方法来执行这个过程:
直接调用
User
类中的隐含静态成员变量class
,这个过程说明class
静态变量定义在每一个普通类中,只是我们无需自行定义,调用方式:Class myClass = User.class;
-
既然有隐含的静态变量
class
,那么就会有隐含的getter
方法,User类的对象就可以通过getter
的方法来获取Class的对象User user = new User();
Class myClass = user.getClass();
使用Class类的静态方法
forName()
来获取Class对象,这个方法接收类的路径,实现运行时动态加载。这个过程需要捕获异常,以便在找不到该类时提供堆栈信息。调用方式:Class myClass = Class.forName(class的路径);
在完成了Class对象获取这一步后,那么就可以开始执行反射的后续阶段了,这里先给出示例的父类Person
和子类User
的代码,注意其中成员变量以及方法的访问限制。并在后面比较了两种方式获取的Class对象。
//这是父类,注意访问限定符
public class Person {
public String mName;
public int mAge;
private String mNickname;
public void say(){
System.out.println("Hello");
}
private void eat(){
System.out.println("eat something");
}
}
//这是子类,注意访问限定符
public class User extends Person{
private String userName;
public int userAge;
private String userInfo;
public void showUserInformation(){
System.out.println("user name is: " + userName);
System.out.println("user age is: " + userAge);
}
private void privateMethod(String str, int num){
System.out.println(str + num);
}
private String getUserName() {
return userName;
}
private void setUserName(String mUserName) {
this.userName = mUserName;
}
private int getUserAge() {
return userAge;
}
private void setUserAge(int mUserAge) {
this.userAge = mUserAge;
}
}
//方法1:获取User类在Class类中的对象classFirst
User user = new User();
Class classFirst = user.getClass();
//方法2:直接通过隐含在Person类中的隐含静态变量class来获取
Class classSecond = User.class;
//查看classFirst和classSecond的类名,是否相同?
System.out.println("user.getClass() 的输出结果: " + classFirst.getName());
System.out.println("User.class的输出结果: " + classSecond.getName());
输出结果表明,获取Class类对象的方法是等效的,都输出了User
这个对象,仍然要注意,这个User
对象的说法是针对Class来讲的。
Java反射--反射成员变量
这一步需要反射出类中的成员变量,通过上面的步骤我们得到了Class的对象User
,那么就可以进行反射的操作了。
//classFirst.getFields()获取子类和父类所有public类型的成员变量
//注意是public标明的成员变量,并且返回的是一个Field数组
Field[] fieldsFirst = classFirst.getFields();
for(Field field : fieldsFirst){
System.out.println(field);
}
System.out.println("**************************************");
ClassFirst
通过调用getFields()
这个方法来获取子类和父类(包括Object类)中所有public类型的成员变量,并且保存在Field
这个数组中,这句话包含了两个信息:
获取了子类
User
和父类Person
乃至Object
类的成员变量获取范围限定在所有的
public
类型中
并且也解释了为何反射与Class无法分割的原因:这个过程就是通过Class的对象调用Class的方法来实现的。这一步仍有个局限,如果我想获取类中的私有变量怎么办?Class类早就为我们考虑好了。
//通过getDeclaredFields()获取User类中声明的变量
//这个方法得到的变量不会受到访问限制符的影响
Field[] fieldsSecond = classFirst.getDeclaredFields();
for(Field field : fieldsSecond){
System.out.println(field);
}
System.out.println("**************************************");
使用getDeclaredFields()
方法便可以获取Class指向该类这个对象的全部成员属性,包括private
、protected
以及public
,上图中也看得出,User
类中的成员变量都被打印出来了。也要注意一点:
-
getDeclaredFields()
不会去父类中包含的成员变量,可以这样理解,declared
表明已经声明,意味着是指Class当前声明的这个对象User
,那么自然就不会去找父类中的成员变量了。
那问题是,目前我只是获得了User
类的成员变量,如果我想获取Person
类的成员变量怎么办?
方法很简单,创建一个指向Person
的Class对象就好了,然后再调用getDeclaredFields()
方法即可得到。
Java反射--反射方法
反射类中的方法与反射成员变量的做法没有什么区别,只是获取方法数组的方法名换了,它也存在着getFields()
和getDeclaredFields()
的同样性质。
//通过classFirst.getMethods()方法获取子类与父类中的所有声明public类型的方法
//注意这个都会包括Object类中的方法
Method[] methodsFirst = classFirst.getMethods();
for(Method method : methodsFirst){
System.out.println(method);
}
System.out.println("**************************************");
图中可以看到清一色的public
修饰的方法,并且得到了Person
和User
类中的两个方法,其余的都是Object
类中的方法,如果想得到User
类中所有的方法,那么和上述的方法思路是一致的。
//同成员变量一致,getDeclaredMethods()方法会返回该类User的所有方法
//这就包括private、protected以及public声明的方法
Method[] methodsSecond = classFirst.getDeclaredMethods();
for(Method method : methodsSecond){
System.out.println(method);
}
System.out.println("**************************************");
通过调用getDeclaredMethods()
便可以得到User
类中的所有方法,包括private
修饰的方法。
通过反射来修改表达
我们通过反射可以获取到类的成员变量信息和方法信息,如果说只是获取,那意义不大。不但要能获取,还能像属于自己的方法一样进行修改表达,那么才有意义。通常private修饰的方法或者变量是无法在外部类正常访问的,但真的无法访问吗?这个过程可以通过反射来实现。
在User
类中新增了一个私有的成员变量userInfo
,并为它配备了相应的getter
和setter
,我们通过反射来将userInfo
这个变量的值从zhuifeng
变为nanfang
。
System.out.println("**************************************");
System.out.println("修改前的userInfo: " + user.getUserInfo());
Field myField = classFirst.getDeclaredField("userInfo");
if(myField != null){
//权限的获取过程
myField.setAccessible(true);
myField.set(user, "nanfang");
System.out.println("修改后的userInfo: " + user.getUserInfo());
}
System.out.println("**************************************");</pre>
这个过程先是通过将userInfo
这个变量名作为参数传递给getDeclaredField
,精准的定位这个成员变量。然后调用setAccessible
来获取其操作权限,最后通过set
方法将user
对象和修改的值传入,通过打印出的结果可以看出,那个被private
修饰的字符串居然被修改成功了!
除了修改被private
修饰的字符串外,被private
修饰的方法也难逃厄运。
Method method = classFirst.getDeclaredMethod("privateMethod", String.class, int.class);
if(method != null){
//可以获取该方法
method.setAccessible(true);
//反射得到的方法调用invoke来执行私有方法
//第一个参数是对应该类的对象,后面是该私有方法的参数
method.invoke(user, "zhuifeng", 1);
}
与调用变量的方法使用相似,getDeclaredMethod
通过传入私有方法的方法名,并将方法参数对应的类作为参数进行依次传递。在IDEA
中当使用这个方法时,输入完该方法的第一个参数方法名后,会自动完成后续的参数填充,很方便。再调用setAccessible
的方法获取权限,就可以调用invoke
方法来执行这个私有方法,从结果的输出来看,这个私有方法的确是被赋值执行了。
Java反射应用场景
给定一个场景:在同一批任务中,比如说有100个任务类,每个任务类都定义了10个同意义但不同表达的成员变量和方法。那么如果不加优化,我们需要在主方法中把每一个类的实现、对象成员变量的赋值和方法的调用全部写出来,这是一个纯粹的体力活,而且效率极其低下。有了反射之后,只需要写一个完整的结构就好了,并且只需要传入Class引用的某个具体的类的对象就可以实现全部功能。在降低了代码的繁琐程度同时,也增加了程序的扩展性,你只需要编写你自己的类就可以了,无需再添加任何代码,剩下的都由反射来完成,非常的高效。
总结
反射是Java的核心知识点,但在日常的开发中,可能用的比较少。在阅读一些框架的源码的时候,倒是可以频繁的看到它们的身影。本文对Java反射做了一个大致的介绍,通过代码实现的方式展示反射的作用,抛砖引玉,以求更深领悟的指导。