Java反射,是框架设计的灵魂。
目录
一、概述
1.1 什么是反射?
将类的各个部分封装成其他对象,这就是反射。有点抽象,不太好理解,我们画个图理解一下。
- 第一个阶段-源代码阶段
定义一个类文件Dog.java,经过javac编译之后生成Dog.class文件,这两个文件都在硬盘上存储。
这个阶段我们可以称之为源代码阶段。
- 第二个阶段-Class类对象阶段
通过类加载器,可以把这个Dog.class字节码文件加载到内存里,但是内存里怎么去描述这个字节码文件呢?在java里万物皆对象,java定了一种Class类对象来描述所有的字节码文件,抽象成共同的特征,比如成员变量,构造方法,成员方法。
在Class类对象中,把成员变量都封装成Field[] fields,把构造方法都分封装成Constructor[] constructors, 把成员方法都封装成Method[] methods。之所以使用数组是因为一般都有多个。基于此,提供出了各式各样的API用来实现只有一个类该具有的操作,其中成员变量可以用来获取和设置值,构造方法可以用来创建对象,成员方法可以来运行和执行。
这个阶段我们称之为Class类对象阶段。
- 第三个阶段-运行时阶段
如直接new出来或者通过反射生成具体对象之后,这时对象已经“运行”在内存中
这个阶段我们称之为运行时阶段
1.2 反射有什么好处?
- 可以在程序运行过程中,操作这些对象,动态获取信息以及动态调用对象方法,这也是为什么称之为框架的灵魂
- 可以解耦,降低程序的紧密连接程度
二、获取字节码Class对象的三种方式
反射是指将一个类的各个部分封装成其他对象,你如何来获取要操作的这个Class类对象,是使用反射的第一步。
具体有三种方式
- Class.forName(“全类名”):将字节码文件加载进内存,返回class对象,对应源代码阶段
- 类名.class:通过类型的属性class直接获取,对应Class类对象阶段
- 对象.getClass():已经有了对象,通过Object中的getClass()方法获取,对应运行时阶段。
与之对应的是上面所讲的三个阶段,如下所示
直接看个例子:
public class ReflectDemo1 {
/**
* 1. Class.forName(“全类名”):将字节码文件加载进内存,返回class对象.
* 2. 类名.class:通过类型的属性class直接获取.
* 3. 对象.getClass():已经有了对象,通过Object中的getClass()方法获取.
*/
public static void main(String[] args) throws ClassNotFoundException {
// 1.Class.forName(“全类名”)
Class cls1 = Class.forName("reflect.Dog");
System.out.println(cls1);
// 2. 类名.class
Class cls2 = Dog.class;
System.out.println(cls2);
// 3. 对象.getClass()
Dog dog = new Dog();
Class cls3 = dog.getClass();
System.out.println(cls3);
// 比较三个对象
System.out.println(cls1 == cls2);
System.out.println(cls1 == cls3);
System.out.println(cls2 == cls3);
}
}
运行结过如下:
这里比较了一下三个对象,发现这三个对象的内存地址都是一样的,说明如下重要结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的class对象都是同一个。
进一步的这三种获取字节码Class对象的方式,具体常用什么场景下。
- Class.forName(“全类名”):多用于配置文件,将类名定义在配置文件中,读取文件,加载类。
- 类名.class:多用于参数的传递。
- 对象.getClass():多用于对象的获取字节码。
三、Class对象功能详解
3.1 概述
Class是一个位于java.lang包下面的一个类,在Java中每个类实例都有对应的Class对象。类对象是由Java虚拟机(JVM)自动构造的。
Class的方法比较多,主要还是以获取为主,对应上面讲的内容,主要是获取成员变量,获取构造器,获取成员方法。常用方法如下所示:
获取功能
-
获取成员变量们
- Field[] getFields() :返回所有public的成员变量
- Field getField(String name) :返回指定名称的public的成员变量
- Field[] getDeclaredFields() :返回所有的成员变量
- Field getDeclaredField(String name) :返回指定名称的成员变量
-
获取构造方法们
- Constructor<?>[] getConstructors() :返回所有public的构造函数
- Constructor getConstructor(Class<?>… parameterTypes) :返回指定参数类型的的public的构造函数
- Constructor<?>[] getDeclaredConstructors() :返回所有构造函数
- Constructor getDeclaredConstructor(Class<?>… parameterTypes) :返回指定参数类型的构造函数
-
获取成员方法们
- Method[] getMethods() :返回所有public的成员方法
- Method getMethod(String name, Class<?>… parameterTypes) :返回指定参数类型的的public的成员方法
- Method[] getDeclaredMethods() :返回所有成员方法,不包括继承的方法
- Method getDeclaredMethod(String name, Class<?>… parameterTypes) :返回指定参数类型的成员方法
-
获取其他
- String getName() :返回此类的完整名称
- Package getPackage() :返回此类的包
- String getSimpleName() :返回此类的简单名称
- ···
判断功能:
- boolean isAnnotation() :是否是注释类型
- boolean isArray() :是否是数组
- boolean isEnum() :是否是枚举类型
- ···
3.2 获取Field详解
3.2.1 获取Field举例说明
首先定义一个Dog类,这里包含了共有和私有的成员变量、构造函数和成员方法。
public class Dog {
private String name;
public int age;
public String type;
/**
* Instantiates a new Dog.
*
* @param name the name
* @param age the age
*/
public Dog(String name, int age, String type) {
this.name = name;
this.age = age;
this.type = type;
}
/** Instantiates a new Dog. */
public Dog() {}
/**
* Gets name.
*
* @return the name
*/
public String getName() {
return name;
}
/**
* Sets name.
*
* @param name the name
*/
private void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" + "name='" + name + '\'' + ", age=" + age + ", type='" + type + '\'' + '}';
}
}
对应的四种获取成员变量的方法使用实例如下。
public class RelectDemo2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class cls1 = Class.forName("reflect.Dog");
// Field[] getFields() :返回所有public的成员变量
System.out.println("-------Field[] getFields() :返回所有public的成员变量-------");
Field[] fields = cls1.getFields();
for (Field f : fields) {
System.out.println(f);
}
// Field getField(String name) :返回指定名称的public的成员变量
System.out.println("-------Field getField(String name) :返回指定名称的public的成员变量-------");
Field filed = cls1.getField("type");
System.out.println(filed);
// Field[] getDeclaredFields() :返回所有的成员变量
System.out.println("-------Field[] getDeclaredFields() :返回所有的成员变量-------");
Field[] declaredFields = cls1.getDeclaredFields();
for (Field f : declaredFields) {
System.out.println(f);
}
// Field getDeclaredField(String name) :返回指定名称的成员变量
System.out.println("-------Field getDeclaredField(String name) :返回指定名称的成员变量-------");
Field declaredfiled = cls1.getDeclaredField("name");
System.out.println(declaredfiled);
}
}
对应的运行结果如下
3.2.2 获取了Field之后有什么用?
但是获取了有什么用的,对于成员变量来说,无非是两种作用获取值和设置值,具体的api可以看Field方法,这里举一个简单例子说明一下即可,详见代码。
涉及到的API
//设置值,其中obj为具体的Dog实例对象,value是想要设置的值
void set(Object obj,Object value)
//获取值,其中obj是具体的Dog实例对象
get(Object obj)
使用代码如下:
public class RelectDemo2 {
public static void main(String[] args)
throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Class cls1 = Class.forName("reflect.Dog");
// Field[] getFields() :返回所有public的成员变量
System.out.println("-------Field[] getFields() :返回所有public的成员变量-------");
Field[] fields = cls1.getFields();
for (Field f : fields) {
System.out.println(f);
}
// Field getField(String name) :返回指定名称的public的成员变量
System.out.println("-------Field getField(String name) :返回指定名称的public的成员变量-------");
Field field = cls1.getField("type");
System.out.println(field);
// 获取成员变量的值
Dog dog1 = new Dog();
Object value1 = field.get(dog1);
System.out.println(value1);
field.set(dog1, "二哈");
value1 = field.get(dog1);
System.out.println(value1);
// Field[] getDeclaredFields() :返回所有的成员变量
System.out.println("-------Field[] getDeclaredFields() :返回所有的成员变量-------");
Field[] declaredFields = cls1.getDeclaredFields();
for (Field f : declaredFields) {
System.out.println(f);
}
// Field getDeclaredField(String name) :返回指定名称的成员变量
System.out.println("-------Field getDeclaredField(String name) :返回指定名称的成员变量-------");
Field declaredfiled = cls1.getDeclaredField("name");
System.out.println(declaredfiled);
// 获取成员变量的值
Dog dog2 = new Dog();
Object value2 = declaredfiled.get(dog2);
System.out.println(value2);
declaredfiled.set(dog2, "汪汪");
value2 = declaredfiled.get(dog2);
System.out.println(value2);
}
运行如下
其中看到使用getField(String name)的方法,正确获取到了null和设置了“二哈”
至于下面的报错,是因为对于私有成员变量默认是没有权限获取和设置的,需要添加如下权限才可以成功进行
Dog dog2 = new Dog();
Object value2 = declaredfiled.get(dog2);
System.out.println(value2);
declaredfiled.setAccessible(true);//非常关键
declaredfiled.set(dog2, "汪汪");
value2 = declaredfiled.get(dog2);
System.out.println(value2);
3.2.3 小结
- 设置值
- void set(Object obj,Object value)
- 获取值
- get(Object obj)
- 忽略访问权限修饰符的安全检查
- setAccessible(true) 暴力反射,真的是想干啥干啥了
3.3 获取Constructor详解
后面就比较类似了,代码简单说明一下
3.3.1 实例说明
public class RelectDemo3 {
public static void main(String[] args)
throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
InvocationTargetException, InstantiationException {
Class cls1 = Class.forName("reflect.Dog");
// 有参构造函数
Constructor constructor = cls1.getConstructor(String.class, int.class, String.class);
System.out.println(constructor);
Object dog = constructor.newInstance("汪汪", 2, "二哈");
System.out.println(dog);
// 无参构造函数
// 方式一,使用Constructor的方法,有点繁琐
Constructor constructor1 = cls1.getConstructor();
System.out.println(constructor1);
Object dog1 = constructor1.newInstance();
System.out.println(dog1);
// 方式二,Class对于无参构造函数直接提供了newInstance()
Object dog2 = cls1.newInstance();
System.out.println(dog2);
}
}
执行结果
3.3.2 小结
- 创建对象
- T newInstance(Object… initargs)
- 如果使用空参数构造方法创建,可以简化成Class对象里的newInstance()方法
- 如果是私有的构造函数,依旧需要忽略访问权限修饰符的安全检查,setAccessible(true) 暴力反射,真的是想干啥干啥了
3.4 获取Method详解
3.4.1 实例说明
public class RelectDemo4 {
public static void main(String[] args)
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
IllegalAccessException {
Class cls1 = Class.forName("reflect.Dog");
// 执行方法
System.out.println("---------------执行方法-----------------");
Method method = cls1.getMethod("play", String.class);
Dog dog = new Dog();
method.invoke(dog, "飞盘");
// 获取所有方法
System.out.println("---------------获取所有方法-----------------");
Method[] methods = cls1.getMethods();
for (Method m : methods) {
System.out.println(m);
}
}
}
执行结果
3.4.2 小结
- 执行方法
- Object invoke(Object obj,Object… args)
- 如果是私有的方法,依旧需要忽略访问权限修饰符的安全检查,setAccessible(true) 暴力反射,真的是想干啥干啥了
四、java反射用在了哪些地方?
- 各种各样的框架里,比如Spring框架
- 各种设计模式里,典型的动态代理模式里
- 各种各样神奇的功能里,比如IDEA里的.提示
五、进阶
如果感兴趣可以继续阅读:
Java反射实战篇<实现一个最最简单的框架>.