1.步入正题之前先扯会淡
之前,在论坛看到别人说过这样一句话:“用专有名词去解释专有名词其实就是一件扯淡的事儿” 。 其实我觉得不无道理,好多技术文档都是用专有名词去解释专有名词,我在看其他博主或者技术文档时也要同时google着里面不理解的一些专有名词。
我个人认为,用自己的语言来描述自己所理解的东西才是最好的,闲聊毕,接下来我们步入正题来学习java反射。
2.java反射的定义
JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
然后,这里要加入我的个人理解: 所谓java反射,其实就是在运行过程中,随便给我一个类,我就可以知道这个类中的所有属性和方法,然后调用这些方法或者修改属性,注意这里指的是运行过程中,也就是.class字节码文件运行在虚拟机的过程中。
考虑一下,如果随便给你一个字节码文件(.class) 你可以用什么方法知道这个字节码文件中有什么方法和属性? 该怎么调用他们? 没错,就是反射。
3.了解定义之后,我们来看一下反射的代码实现和使用
反射的用途有很多,下面我只是介绍最简单的用途,如果你可以理解下面的例子,我想你可以利用反射模拟Spring管理beans(当然这可能需要用到一些其他的知识)
(1).如何反射获得一个类的字节码文件
这里我们需要知道java为我们提供了一个类叫做Class 我们来看一下官方文档的描述(jdk1.9) :
大概的意思是说Class类的实例表示一个正在运行的Java类或者接口,也就是我们说的已经加载到jvm中的.class文件对象。 并且文中有提到该类没有公共的构造方法,也就是说不允许我们new该类对象,原因很简单,我们不能自己创造class字节码文件,这些工作都有java编译器和虚拟机来进行。
说的好不如做的好,接下来,我们开始码代码
新建一个项目,该项目中包含两个类,一个是People类(被反射的类) 一个是测试类
People类代码:
package com.javastudy.reflect;
/**
*
* @author stranger_bai
* 演示类
*/
public class People {
public String name;
public int age;
private String sex;
public void show() {
System.out.println("i am "+name+" , i am "+age+" years old and i am a"+sex);
}
public void run() {
System.out.println("i can run");
}
public void swim() {
System.out.println("i can swim");
}
public void sayHello(String s) {
System.out.println( s+" say hello to me !");
}
}
注意,People类没有实际意义,只是用来做一个演示。 接下来我们来介绍如何获得People的字节码对象Class
第一种方式: Class类的静态方法 forName()
第二种方式: 直接 使用 类名.class 获取 两种方式获取的是同一个对象
package com.javastudy.reflect;
/**
*
* @author stranger_bai
*测试类
*/
@SuppressWarnings("all")
public class Test {
public static void main(String[] args) throws Exception{
Class peopleClass = Class.forName("com.javastudy.reflect.People"); //第一种 forName 获取
Class secondClass = People.class; //第二种,直接类名.class获取
System.out.println(peopleClass == secondClass); //true 获取到的是同一对象
}
}
(2)获取到字节码后,我们就可以获取该类的方法和属性了
既然是Class类对象,那我们就要看看Class类都为我们提供了那些方法。
(这里我们只关心获取属性和方法的方法,其余的方法请自行理解)
以上这几个方法就是我们用来获得属性和方法的方法,当然还有其他的方法,这里不再赘述。
这里演示一个获取方法并执行的例子,其余大同小异,请读者自行尝试,如果有问题,欢迎讨论!
首先,我们看到,Class为我们提供了两个获取方法的方法,一个是getMethod 另一个是getMethods 顾名思义,一个是获取所有方法,一个是获取单一方法。获取单一方法又要求我们传入至少一个参数name 然后还需要再传入一个parameterTypes可变参数,其中name指的是方法名,后面的可变参数指的是方法的参数(这里是为了区分重载方法)。返回值都是Method类型
接下来,测试类代码:
package com.javastudy.reflect;
import java.lang.reflect.Method;
/**
*
* @author stranger_bai
*测试类
*/
@SuppressWarnings("all")
public class Test {
public static void main(String[] args) throws Exception{
Class peopleClass = Class.forName("com.javastudy.reflect.People"); //第一种 forName 获取
Class secondClass = People.class; //第二种,直接类名.class获取
System.out.println(peopleClass == secondClass); //true 获取到的是同一对象
Method[] methods = peopleClass.getMethods(); //获取所有方法
for(Method m:methods) {
System.out.println(m.getName()); //遍历输出所有获取到的方法名
}
Method showmethod = peopleClass.getMethod("show"); //通过方法名获取方法
System.out.println(showmethod.getName());
}
}
我们来看一下输出结果:
我们发现,不仅仅是我们写的方法被获取到,连继承自object类的方法也会被找到。 也就是说,不论我们进行多少次继承,其父类的方法都会被找到(请大家自行测试)
获取属性这里不再赘述,利用getFields和getField 返回Field类实例,具体操作参考Field类方法
(3)获取到方法或者属性,怎么调用方法或者修改属性?
我们知道,获得的方法是Method类的实例,那我们不妨去看一下Method类的文档
同样,Method类也有很多方法,我们只介绍我们要用到的invoke方法,这个方法是用来调用当前对象所代表的方法。参数obj是方法所在类的对象(这里比较复杂,涉及到对象的单例和多例,所以需要知道要调用的是哪个实例的哪个方法),参数args是我们传给被调用方法的参数。
那么,至此为止,我们要想调用方法,还有一个问题没解决,就是怎么实例化方法所在类对象 ,这里有两种方式,还记得我们上面在获取方法时有一个getConstructor 返回一个 Constructor对象吗,Constructor类提供一个newInstance 方法返回一个该类的实例
或者我们可以直接使用Class类提供的newInstance方法,都可以返回该类实例,那么我们有了该类的实例,有了要调用的方法的Method实例,我们就可以通过invoke调用该方法了!
package com.javastudy.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
*
* @author stranger_bai
*测试类
*/
@SuppressWarnings("all")
public class Test {
public static void main(String[] args) throws Exception{
Class peopleClass = Class.forName("com.javastudy.reflect.People"); //第一种 forName 获取
Class secondClass = People.class; //第二种,直接类名.class获取
System.out.println(peopleClass == secondClass); //true 获取到的是同一对象
Method[] methods = peopleClass.getMethods(); //获取所有方法
for(Method m:methods) {
System.out.println(m.getName()); //遍历输出所有获取到的方法名
}
Method showmethod = peopleClass.getMethod("show"); //通过方法名获取方法
System.out.println(showmethod.getName());
//调用方法
//1.获取方法所在类实例
People p1 = (People)peopleClass.newInstance(); //方式一
Constructor con = peopleClass.getConstructor(); //方式二 参数是构造器参数,这里构造器没有参数
People p2 = (People)con.newInstance();
//2.获取到要调用的方法
Method helloMethod = peopleClass.getMethod("sayHello", String.class);
//3.调用invoke
helloMethod.invoke(p2, "小明");//执行p2 对象的sayHello方法,方法的参数是 小明
}
}
那么至此我们就成功利用反射调用到了一个类的方法。属性操作也是大同小异,具体请参考官方jdk文档所提供的方法。
4.举一反三
1. 我们知道,java中的泛型是不会被编译到class文件中去的,也就是说泛型只在源码阶段存在,反射是在运行时起作用,那么大家可以尝试一下向某个泛型的集合中插入不是该泛型的数据(例如向String集合中插入Integer)当然,这可能没有意义,但是建议大家尝试一下 。(泛型擦除)
2.编写一个程序,运行时动态输入类名、方法和属性名,动态输入参数,实现调用。 (基本 类似Spring框架管理bean)
3. 参考Field类方法,实现修改peivate 属性的值(不使用get/set访问器) (暴力反射)
文章属于个人理解,若有错误,欢迎指正,欢迎交流 。
JDK1.9 官方文档: https://docs.oracle.com/javase/9/docs/api
JDK1.9 中文翻译版: https://pan.baidu.com/s/1E8U2_Ckzq-AHX2bZtE6NkA 密码 ds29