Java

Java反射-获取对象的点点滴滴

2019-08-03  本文已影响3人  windytouch

文章首发至个人公众号:追风栈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类来讨论这个问题。

有三种方法来执行这个过程:

在完成了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这个数组中,这句话包含了两个信息:

并且也解释了为何反射与Class无法分割的原因:这个过程就是通过Class的对象调用Class的方法来实现的。这一步仍有个局限,如果我想获取类中的私有变量怎么办?Class类早就为我们考虑好了。

//通过getDeclaredFields()获取User类中声明的变量
//这个方法得到的变量不会受到访问限制符的影响
Field[] fieldsSecond = classFirst.getDeclaredFields();
for(Field field : fieldsSecond){
 System.out.println(field);
}
System.out.println("**************************************");

使用getDeclaredFields()方法便可以获取Class指向该类这个对象的全部成员属性,包括privateprotected以及public,上图中也看得出,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修饰的方法,并且得到了PersonUser类中的两个方法,其余的都是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,并为它配备了相应的gettersetter,我们通过反射来将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反射做了一个大致的介绍,通过代码实现的方式展示反射的作用,抛砖引玉,以求更深领悟的指导。

参考资料

Java反射由浅入深
浅谈Java中的Class类

上一篇 下一篇

猜你喜欢

热点阅读