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

文章首发至个人公众号:追风栈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指向该类这个对象的全部成员属性,包括privateprotected以及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修饰的方法,并且得到了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类

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容