一、反射的目的是什么?
Java的权限机制限制了对一个类的私有成员的访问。如果想要访问或修改一个对象的私有成员或者方法,则可以通过反射来实现。
通过反射机制,可以在运行中获取到一个类的属性和方法并调用之。
反射相关的类在java.lang.reflect
包中。
二、反射的具体使用示例
反射主要用到四个类,Class、Field、Method、Constructor,分别代表类、属性、方法、构造方法。
现有类Animal
如下,本章以Animal
类为例:
class Animal {
private String name;
private Animal() {
}
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void run() {
System.out.println(name + " is running");
}
private void die() {
System.out.println(name + " died");
}
}
加载类
对于一个非公开的类,在其他包无法访问。这时需要使用反射,通过Class.forName()
来获取到类。
Class animalClass = Class.forName("com.example.Animal");
Class.forName
本质上是通过当前类加载器加载类。
获取并修改对象成员的值
对于Animal
类型的变量animal
,现在需要修改其name
属性。
// 通过反射修改name
private static void modifyName(Animal animal) throws NoSuchFieldException, IllegalAccessException {
Class cls = Animal.class; // 获取Class对象
Field nameField = cls.getDeclaredField("name"); // 获取name属性的Field对象
nameField.setAccessible(true); // 设置name权限为可获取
nameField.set(animal, "pig"); // 修改animal的名称为"pig"
System.out.println(animal.getName()); // 打印名称
}
调用私有方法
对于Animal
类的die()
方法,由于其是私有的,所以需要通过反射调用。
// 通过反射调用私有方法
private static void invokePrivateMethod(Animal animal) throws Exception {
Class cls = Animal.class; // 获取Class对象
Method method = cls.getDeclaredMethod("die"); // 获取die()方法的Method对象
method.setAccessible(true); // 设置方法可以调用
method.invoke(animal); // 调用方法
}
通过私有构造方法创建对象
由于Animal
类的无参构造方法是私有的,如果要使用这个方式构建对象,需要使用反射。
// 通过私有的无参构造方法创建Animal对象
private static Animal constructInstance() throws Exception {
Class cls = Animal.class; // 获取Class对象
Constructor constructor = cls.getDeclaredConstructor(); // 获取无参构造方法
constructor.setAccessible(true); // 设置该构造方法可调用
return (Animal) constructor.newInstance(); // 创建实例并返回
}
三、反射常用方法
1. Class类
Class.forName(String)
通过类的全名加载类。与直接通过类加载器加载类的区别是,Class.forName
会初始化类的static内容,而ClassLoader不会。
getDeclaredMethod / getMethod / getDeclaredMethods / getMethods
获取该类的方法。
其中getDeclaredMethod(methodName, paramTypes)
获取的是类自身声明的方法,包含public、protected和private方法;getMethod(methodName, paramTypes)
获取的是类的public方法,包括自身的和从父类、接口继承的。
与此类似的,getDeclaredMethods()
获取所有该类声明的方法,而getMethods()
获取该类声明的和继承、实现的所有public方法。
getDeclaredField / getField / getDeclaredFields / getFields
获取该类的字段(成员变量、静态变量),其间的区别与获取方法相同。
getConstructor / getConstructors / getDeclaredConstructor / getDeclaredConstructors
获取该类的构造方法,其间的区别与获取方法相同。
2. Field类
get(obj)
获取实例obj
中该字段的值。如果是静态变量,则obj
传入null。
set(obj, value)
设置实例obj
中该字段的值。如果是静态变量,则obj
传入null。
setAccessible(boolean)
设置该字段是否默认可访问。如果设置为true
,则这个字段会忽略Java访问修饰符。
3. Method类
invoke(obj)
调用obj
对象的该方法。如果是静态方法,则传入null。
setAccessible(boolean)
设置该方法是否默认可访问。如果设置为true
,则这个字段会忽略Java访问修饰符。
4. Constructor类
newInstance(params)
通过该构造方法创建对象。
setAccessible(boolean)
设置该构造方法是否默认可访问。如果设置为true
,则这个字段会忽略Java访问修饰符。
四、为什么反射效率低?如何提高反射效率?
反射效率低的原因:
- 会做一系列的权限判断、包装参数、检查参数等额外工作;
- JIT无法优化;
提高反射效率的方式:
- 对反射得到的Class、Method、Field、Constructor等对象进行缓存;
- 使用MethodHandle;
- 使用第三方库如ASM;
网友评论