1. 为什么使用反射修改目标属性值?
为什么要使用反射修改目标对象的指定字段值?直接通过对象去设置或者直接修改代码设置字段值不香吗?
答:
- 有的时候,我们需要修改一个成员变量的值,但是该成员在jar包中或者在其他位置且没有提供对应的修改方法或者成员是私有的,导致我们不能从代码层面修改。
- 有的时候,代码是别人提供的,并且成员定义成了private私有的且没有提供setter方法,而公司规则不允许直接修改别人提供的代码。
基于这些常见的原因,因此可能需要我们通过非常规手段去修改目标对象的指定成员值。
使用反射修改目标类字段的核心API是Field类的set()方法:
public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException;
这个方法我们需要注意的是:如果底层需要设置的字段是static的话,则第一个参数可以忽略,直接传null值即可。
这个特性很重要,意味着我们通过反射设置一个static字段的值的时候,可以不用传递实例对象进去,比如反射设置接口中的常量字段,我们无法传递一个接口实例进去,就可以直接传递null。
实际上对于底层字段是static的,传递一个明确的实例对象进去毫无意义,因为static字段不是实例对象自己的,而是隶属于类。
1.1 使用反射设置普通成员变量的值
对于一个javabean来说,其存在普通的字段。一般来讲,对一个目标对象的指定字段进行值的重置操作的话,存在两种常见的额方式:
- 直接反射目标字段,强制赋值;
- 如果目标字段有指定的setter之类的赋值方法,则可以反射该方法进行赋值。
很明显,对于那种没有setter方法的private成员,只能采用第一种方式进行赋值了。
通过反射修改普通成员:
package zeh.test.demo.com.fanshe;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class FanSheMain {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
// 使用反射修改目标对象指定字段的值。
Cat cat = new Cat();
cat.setName("奶茶");
System.out.println(cat);
// 方式1:获取目标字段的field
Field nameField = Cat.class.getDeclaredField("name");
// 设置对该字段可访问
nameField.setAccessible(true);
// 对cat对象的当前属性进行赋值操作
nameField.set(cat, "珍珠");
System.out.println(cat);
// 方式2:获取目标setter方法
Method setNameMethod = Cat.class.getDeclaredMethod("setName", String.class);
setNameMethod.invoke(cat, "helloworld");
System.out.println(cat);
}
}
class Cat {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return "name = " + this.getName();
}
}
测试结果:
name = 奶茶 name = 珍珠 name = helloworld
在反射API中,凡是“getDeclared***”的方法,比如getDeclaredField、getDeclaredMethods等,获取的都是目标类中的所有方法,比如public、private等,只要目标类中有的方法和字段,都会被获取到。但是不能获取从父类继承而来的方法和字段。
凡是getField、getMethods等方法,获取的都是目标类中的所有public公共方法,包括从父类整个继承链路中继承而来的所有的public方法。但是仅仅只能获取public方法。
所以,当我们有复杂的需求时,比如想要获取某个目标类的所有方法或者成员,以及整个继承链路上的所有方法或者成员,此时就需要采用两者结合的方式来实现:
先获取目标class的直接父类,使用class.getSuperClass()方法进行获取,然后递归获取父类的父类,直到最终获取到的直接父类为null(这个null肯定就是Object类的直接父类了)就表示递归完毕。
同时,一个目标class可能有实现多个接口,通过clazz.getInterfaces()方法获取目标类的所有接口。
以下是chatgpt给出的案例。
要获取一个类的所有父类和实现的接口,可以使用 Java 反射机制提供的 getSuperclass()
和 getInterfaces()
方法。以下是获取一个类的所有父类和接口的代码示例:
1. 获取所有父类
通过递归调用 getSuperclass()
方法,可以获取目标类的所有父类。
2. 获取所有接口
通过递归调用 getInterfaces()
方法,可以获取目标类及其父类实现的所有接口。
示例代码
import java.util.ArrayList;
import java.util.List;
public class ClassHierarchyUtils {
// 获取所有父类
public static List<Class<?>> getAllSuperclasses(Class<?> clazz) {
List<Class<?>> superClasses = new ArrayList<>();
while (clazz.getSuperclass() != null) {
clazz = clazz.getSuperclass();
superClasses.add(clazz);
}
return superClasses;
}
// 获取所有实现的接口
public static List<Class<?>> getAllInterfaces(Class<?> clazz) {
List<Class<?>> interfaces = new ArrayList<>();
while (clazz != null) {
for (Class<?> i : clazz.getInterfaces()) {
if (!interfaces.contains(i)) {
interfaces.add(i);
// 递归获取父接口
interfaces.addAll(getAllInterfaces(i));
}
}
clazz = clazz.getSuperclass();
}
return interfaces;
}
public static void main(String[] args) {
// 示例类
Class<?> clazz = ArrayList.class;
// 获取所有父类
List<Class<?>> superClasses = getAllSuperclasses(clazz);
System.out.println("All Superclasses:");
for (Class<?> superClass : superClasses) {
System.out.println(superClass.getName());
}
// 获取所有接口
List<Class<?>> interfaces = getAllInterfaces(clazz);
System.out.println("All Interfaces:");
for (Class<?> interfaceClass : interfaces) {
System.out.println(interfaceClass.getName());
}
}
}
示例输出
假设我们使用 ArrayList
类作为目标类,输出可能会是这样的:
All Superclasses:
java.util.AbstractList
java.util.AbstractCollection
java.lang.Object
All Interfaces:
java.util.List
java.util.RandomAccess
java.lang.Cloneable
java.io.Serializable
解释
获取所有父类:
getAllSuperclasses
方法会从目标类开始,沿着继承链不断向上获取所有的父类,直到没有父类为止(即父类为null
,通常是Object
类)。获取所有接口:
getAllInterfaces
方法不仅会获取目标类直接实现的接口,还会递归地获取父类和父接口实现的所有接口。
注意事项
- 类的层次结构:该方法会递归获取整个类的层次结构和接口实现情况,因此对于较复杂的类可能会涉及到很多父类和接口。
- 循环依赖:Java 不支持类的循环继承,因此不必担心循环依赖问题。
1.2 使用反射修改final常量成员
同样,使用反射也可以重置目标对象的指定final成员的值。
tips:
不是说final修饰的常量其值不允许修改么?
答案:我们前面深入了解过,final修饰的常量其值不允许修改是java编译器阶段禁止的,也就说JVM实际上并没有禁止修改final变量值的行为。
所以,通过反射是可以修改final常量的值的,因为反射操作可以绕过java编译器的检查。
通过反射修改final常量的值:
package zeh.test.demo.com.fanshe;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
public class FanSheMain {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
// 使用反射修改目标对象指定字段的值。
Cat cat = new Cat();
System.out.println(cat);
// 获取目标字段的field
Field nameField = Cat.class.getDeclaredField("stringBuilder");
// 设置对该字段可访问
nameField.setAccessible(true);
// 对cat对象的当前属性进行赋值操作
nameField.set(cat, new StringBuilder("珍珠"));
System.out.println(cat);
}
}
class Cat {
private final StringBuilder stringBuilder = new StringBuilder("奶茶");
@Override
public String toString() {
return "name = " + stringBuilder.toString();
}
}
测试结果:
name = 奶茶
name = 珍珠
结果分析: 从结果上看,通过反射修改了目标对象cat的final常量stringBuilder的值。
而stringBuilder是个引用对象,比如它在反射前指向的是A这个空间,反射后让它指向了B。即改变了它的内存指向。
因此:通过反射可以修改final成员的值。
1.3 使用反射修改final常量成员-针对简单类型
java中对于基本类型和String,在使用final操作时,如果是直接赋值的方式进行初始化,则称这些值为字面量。
比如:
private final int a = 10;
private final String str = "eric";
像这种直接给简单类型初始化为字面量的操作,java编译器会进行一种优化,叫做宏替换。
即在后续所有使用到a和str的地方,都会直接替换为其对应的字面量,而a和str实际上在编译阶段就已经明确了,它们不会参与JVM中的逻辑,只要用到它们,就是对应的字面量。
好了,有了这个基础,实际上我们已经知道结论了:对于基本数据类型和String的final常量值,只要在编译阶段能够明确它们的值,则通过反射无法更改他们:
反射无法修改编译期间已经明确字面量值的final字段,因为编译器会进行宏替换操作:
package zeh.test.demo.com.fanshe;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
public class FanSheMain {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
// 使用反射修改目标对象指定字段的值。
Cat cat = new Cat();
System.out.println(cat);
// 获取目标字段的field
Field age1Field = Cat.class.getDeclaredField("age1");
Field age2Field = Cat.class.getDeclaredField("age2");
Field name1Field = Cat.class.getDeclaredField("name1");
Field name2Field = Cat.class.getDeclaredField("name2");
// 设置对该字段可访问
age1Field.setAccessible(true);
age2Field.setAccessible(true);
name1Field.setAccessible(true);
name2Field.setAccessible(true);
// 对cat对象的当前属性进行赋值操作
age1Field.set(cat, 20);
age2Field.set(cat, 28);
name1Field.set(cat, "珍珠");
name2Field.set(cat, "珍珠大哥");
System.out.println(cat);
// 尽管age1和name1看起来使用反射修改失败了,但我们知道那实际上是编译器的优化导致的,在JVM运行中反射到底对这两个字段值修改成功了么?
// 我们使用反射再取出来观察一下:
Object age1 = age1Field.get(cat);
Object name1 = name1Field.get(cat);
// 观察结果,实际上反射操作完全是修改成功了。只不过编译器优化后,在所有使用age1和name1的地方全部替换成了编译期常量了,没有使用修改后的值而已。
System.out.println(age1);
System.out.println(name1);
}
}
class Cat {
// age1是基本类型且直接初始化赋值,其值在编译阶段已经明确,就是10,因此后续用到age1的地方编译器都直接替换成了该常量值
private final int age1 = 10;
// age2是Integer类型的,是引用类型,此处编译器会进行默认装箱操作,即执行Integer.valueOf(18);因此实际上age2此时的值并不明确,编译器会保存它的符号引用,在JVM运行后再替换为直接引用
private final Integer age2 = 18;
// name1直接初始化赋值,编译期间可以确定其值为“奶茶”,因此后续所有用到name1常量的地方都直接优化了,替换成了对应的字面量"奶茶"
private final String name1 = "奶茶";
// 同样,强制采用构造器的方式,初始化后name2实际上是个引用,因此编译器保存的是对应的符号引用,等JVM运行可以确定直接引用后再进行替换
private final String name2 = new String("奶茶妹子");
// 所以,结果就是:只要是编译期间可以确定的字面量常量,反射都不能修改其值;否则,反射可以修改,因为它们参与了JVM的运行过程。
@Override
public String toString() {
return "age = " + age1 + ";age2 = " + age2 + ";name1 = " + name1 + ";name2 = " + name2;
}
}
测试结果:
age = 10;age2 = 18;name1 = 奶茶;name2 = 奶茶妹子
age = 10;age2 = 28;name1 = 奶茶;name2 = 珍珠大哥
20
珍珠
结果分析:
- 编译器进行宏替换的final常量字段,反射无法修改,因为这些常量在编译阶段就明确了,不参与JVM的动态操作。
- 编译器不进行宏替换的其他字段,比如普通的成员变量和那些引用类型的常量字段,通过反射可以修改,因为它们需要在JVM中进行符号引用向直接引用的替换操作,因此反射可以操作它们。
- 实际上反射修改常量,已经修改成功了,通过调试,可以看到在JVM内存中目标字段的值已经是修改后的;只是对于那些编译阶段明确字面量的常量,编译器进行宏替换优化了,没有使用反射修改后的常量值而已。
所以,我们在通过反射修改目标字段值时,需要记住:
java编译器本身对基本类型和String的final常量进行了优化操作(宏替换),目的是提升运行效率。
但从表现上看,这种优化实际上会导致反射此常量时给人反射失效的错觉。
这个问题无解。
1.4 使用反射修改static成员变量
- static修饰成员变量后,该成员变量初始化早;随着目标类的装载只初始化一次。
- static修饰的成员存储在方法区内存的静态区中,是被所有实例对象共享的数据区域。
尽管static成员和实例成员有如上主要区别,但static成员仍旧参与JVM的动态操作,也就是说,代码中凡是用到static成员的地方,它依旧是个变量而已。
因此,通过反射就可以修改它的值。
反射修改static成员的值:
```java package zeh.test.demo.com.fanshe; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException;
public class FanSheMain { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { // 使用反射修改目标对象指定字段的值。 Cat cat = new Cat(); System.out.println(cat); // 获取目标字段的field Field nameField = Cat.class.getDeclaredField(“name”); // 设置对该字段可访问 nameField.setAccessible(true); // 对Cat类的静态属性进行赋值操作,注意第一个参数可以直接传null。 nameField.set(null, “珍珠”); System.out.println(cat); } } class Cat { private static String name = “奶茶”; @Override public String toString() { return “name = “ + name; } }
测试结果:
name = 奶茶 name = 珍珠
## 1.5 使用反射修改static final成员变量
JVM底层对于static final的成员始终是不可访问的,即便使用 field.setAccessible(true);设置也不行。
<b>反射修改static final成员的值:</b>
```java
package zeh.test.demo.com.fanshe;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
// 2020-07-15
public class FanSheMain {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
// 使用反射修改目标对象指定字段的值。
Cat cat = new Cat();
System.out.println(cat);
// 获取目标字段的field
Field nameField = Cat.class.getDeclaredField("name");
// 设置对该字段可访问
nameField.setAccessible(true);
// 对Cat类的静态属性进行赋值操作,注意第一个参数可以直接传null。
nameField.set(null, new StringBuilder("珍珠"));
System.out.println(cat);
}
}
class Cat {
// 初始化一个static final的常量,因为是final的,所以使用 StringBuilder 代替 String
private static final StringBuilder name = new StringBuilder("奶茶");
@Override
public String toString() {
return "name = " + name.toString();
}
}
测试结果:
Exception in thread "main" java.lang.IllegalAccessException: Can not set static final java.lang.StringBuilder field zeh.test.demo.com.fanshe.Cat.name to java.lang.StringBuilder
at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)
at java.lang.reflect.Field.set(Field.java:764)
at zeh.test.demo.com.fanshe.FanSheMain.main(FanSheMain.java:22)
1.6 使用反射修改static final成员变量-改造
改造上面案例:
- 获取指定的static final的字段Field对象,并且设置为可访问。
- 通过反射,去除指定字段的final修饰符。
- 成功修改指定final static字段的值。
- 修改完成后再通过反射将final修饰符加回来。
反射修改static final成员的值:package zeh.test.demo.com.fanshe; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; // 2020-07-15 public class FanSheMain { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { Cat cat = new Cat(); System.out.println(cat); // 1. 获取目标字段的field,并设置可访问权限 Field nameField = Cat.class.getDeclaredField("name"); nameField.setAccessible(true); // 2. 获取当前field对象中的modifiers成员的值,即当前field成员的所有java修饰符,注意该修饰符是一个使用Modifier编码后的整数。 Field modifiersField = nameField.getClass().getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.set(nameField, nameField.getModifiers() & ~Modifier.FINAL); // 3. 对Cat类的静态属性进行赋值操作,注意第一个参数可以直接传null。 nameField.set(null, new StringBuilder("珍珠")); // 4. 将final修饰符再添加回去 modifiersField.set(nameField, nameField.getModifiers() & ~Modifier.FINAL); System.out.println(cat); } } class Cat { // 初始化一个static final的常量,因为是final的,所以使用 StringBuilder 代替 String private static final StringBuilder name = new StringBuilder("奶茶"); @Override public String toString() { return "name = " + name.toString(); } }
测试结果:
name = 奶茶 name = 珍珠
2. 使用反射调用目标对象的指定方法
2.1 为什么要使用反射调用目标对象的方法?
目标对象的方法可能是private的,我们无法直接调用的情况下就可以使用反射强制调用。
使用反射调用目标方法的核心API是Method类的invoke()方法:
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
同File一样,这个方法我们需要注意的是,如果要调用的底层方法是static方法的话,则第一个参数obj可以忽略,直接传递null值即可。
2.2 反射执行目标对象各种方法
有的时候,目标方法是private的,只能本类自己调用。这时候如果想要调用private方法,可以采用反射强制调用执行。
package zeh.test.demo.com.fanshe;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
// 2020-07-15
public class FanSheMain {
public static void main(String[] args) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
// 实例化目标对象
Cat cat = new Cat();
Method testMethod = Cat.class.getDeclaredMethod("test");
testMethod.invoke(cat);
Method test1Method = Cat.class.getDeclaredMethod("test1");
// 设置当前方法可访问
test1Method.setAccessible(true);
test1Method.invoke(cat);
Method test2Method = Cat.class.getDeclaredMethod("test2", String.class);
// 设置当前方法可访问
test2Method.setAccessible(true);
test2Method.invoke(cat, "奶茶");
Method test3Method = Cat.class.getDeclaredMethod("test3", String.class);
// 设置当前方法可访问
test3Method.setAccessible(true);
Object result = (String) test3Method.invoke(cat, "珍珠");
System.out.println("result:" + result);
Method test4Method = Cat.class.getDeclaredMethod("test4", String.class);
// 设置当前方法可访问
test4Method.setAccessible(true);
result = (String) test4Method.invoke(cat, "eric");
System.out.println("result:" + result);
Method test5Method = Cat.class.getDeclaredMethod("test5", String.class);
// 设置当前方法可访问
test5Method.setAccessible(true);
result = (String) test5Method.invoke(cat, "daisy");
System.out.println("result:" + result);
Method test6Method = Cat.class.getDeclaredMethod("test6", String.class);
// 设置当前方法可访问
test6Method.setAccessible(true);
result = (String) test6Method.invoke(cat, "jay");
System.out.println("result:" + result);
}
}
class Cat {
// 无返回值,实例方法,没有方法参数,public修饰
public void test() {
System.out.println("test方法");
}
// 无返回值,实例方法,没有方法参数,private修饰
private void test1() {
System.out.println("test1方法");
}
// 无返回值,实例方法,有方法参数,private修饰
private void test2(String name) {
System.out.println("test2方法:" + name);
}
// 有返回值,实例方法,有方法参数,private修饰
private String test3(String name) {
System.out.println("test3方法:" + name);
return name;
}
// 有返回值,static方法,有方法参数,private修饰
private static String test4(String name) {
System.out.println("test4方法:" + name);
return name;
}
// 有返回值,实例方法,有方法参数,private修饰,final修饰
private final String test5(String name) {
System.out.println("test5方法:" + name);
return name;
}
// 有返回值,实例方法,有方法参数,private修饰,final修饰
private final static String test6(String name) {
System.out.println("test6方法:" + name);
return name;
}
}
反射目标类的指定方法,还有个特别的用法,那就是method对象可以通过反射目标接口来获得,然后使用获得后的method对象,可以执行该接口对应的实现类的指定方法。
这一点在jdk的动态代理中很有用。
目标接口实现类FanSheServiceImpl:
package zeh.test.demo.com.fanshe;
// 2021-05-19
public class FanSheServiceImpl implements FanSheService {
@Override
public void method1() {
System.out.println("我是method1");
this.method2();
}
@Override
public void method2() {
System.out.println("我是method2");
}
}
FanSheServiceMain测试:
package zeh.test.demo.com.fanshe;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
// 2021-05-19
public class FanSheServiceMain {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 下面获取的是接口的目标方法 method1
Method method1 = FanSheService.class.getMethod("method1");
// 通过接口的method1描述,在实例对象FanSheServiceImpl上去执行目标方法
method1.invoke(new FanSheServiceImpl());
}
}
测试结果:
我是method1
我是method2
3. 使用反射实例化目标对象
3.1 为什么使用反射实例化目标对象?
- 当目标类的构造方法是私有的,最典型的就是单例模式中,这时候外部无法创建目标类的对象。
- 目标类通过多态,采用某种通用的形式统一接收,这时候想要统一的创建目标类的对象的话,就采用反射统一创建即可。
3.2 使用Class的newInstance()方法创建目标实例对象
观察Class类的newInstance()方法的API解释:
- newInstance()方法用于创建当前class对象描述的类的一个实例化对象。
- 其默认调用当前Class的public的空构造方法去创建实例,如果当前类没有加载,则触发JVM加载该类。
- 如果当前Class没有空构造方法或者空构造方法是不可访问的,将抛出异常。
- 如果当前Class是抽象类、接口、数组、基本类型或者void,或者该类没有空构造方法,则抛出异常。
因此:使用Class类的newInstance()方法去创建目标类对象的方式很不实用,因为它要求目标类必须存在空的可访问的构造方法。
使用Class的newInstance()反射创建目标类实例对象:
package zeh.test.demo.com.fanshe;
// 2020-07-15
public class FanSheMain {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Cat cat = (Cat) createInstance(Cat.class);
System.out.println(cat);
}
// 实现一个通用方法去创建任意目标类的实例对象
public static Object createInstance(Class<?> clazz) throws IllegalAccessException, InstantiationException {
Object object = clazz.newInstance();
return object;
}
}
class Cat {
// Class的newInstance()方法反射创建当前类对象,要求当前类必须存在可访问的空构造方法
public Cat() {
}
@Override
public String toString() {
return "当前对象创建成功";
}
}
测试结果:
当前对象创建成功
修改程序:将上面的Cat类的构造方法改造为私有的,将抛出异常:
Exception in thread "main" java.lang.IllegalAccessException: Class zeh.test.demo.com.fanshe.FanSheMain can not access a member of class zeh.test.demo.com.fanshe.Cat with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.Class.newInstance(Class.java:436)
at zeh.test.demo.com.fanshe.FanSheMain.createInstance(FanSheMain.java:17)
at zeh.test.demo.com.fanshe.FanSheMain.main(FanSheMain.java:11)
或者删除空构造方法,添加一个有参数的构造方法:
Exception in thread "main" java.lang.InstantiationException: zeh.test.demo.com.fanshe.Cat
at java.lang.Class.newInstance(Class.java:427)
at zeh.test.demo.com.fanshe.FanSheMain.createInstance(FanSheMain.java:17)
at zeh.test.demo.com.fanshe.FanSheMain.main(FanSheMain.java:11)
Caused by: java.lang.NoSuchMethodException: zeh.test.demo.com.fanshe.Cat.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.newInstance(Class.java:412)
... 2 more
3.3 使用Constructor类的newInstance()创建目标类实例对象
- 获取到目标类的Constructor对象,即获取目标类指定的构造器描述对象。
- 通过该构造器描述对象,调用newInstance()方法,传入指定构造器的参数,即可调用目标类指定的构造器去创建目标类对象。
反射调用指定构造器对象去创建目标类实例:
package zeh.test.demo.com.fanshe;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
// 2020-07-15
public class FanSheMain {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Constructor constructor = getConstructor(Cat.class, String.class);
Cat cat = (Cat) createInstance(constructor, "珍珠");
System.out.println(cat);
}
// 实现一个通用方法去创建任意目标类的实例对象
public static Object createInstance(Constructor<?> constructor, Object... args) throws IllegalAccessException, InvocationTargetException, InstantiationException {
Object instance = constructor.newInstance(args);
return instance;
}
// 实现一个通用方法区获取任意目标类的指定构造器描述
public static Constructor<?> getConstructor(Class<?> clazz, Class<?>... parameterTypes) throws NoSuchMethodException {
Constructor<?> constructor = clazz.getDeclaredConstructor(parameterTypes);
// 如果目标构造器是不可访问的,此处需要设置可访问权限
constructor.setAccessible(true);
return constructor;
}
}
class Cat {
private String name;
private Cat(String name) {
this.setName(name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "name = " + this.getName();
}
}
测试结果:
name = 珍珠
文档信息
- 本文作者:Marshall