反射的常见操作

2021/02/16 Java初级进阶 共 17685 字,约 51 分钟
闷骚的程序员

1. 为什么使用反射修改目标属性值?

为什么要使用反射修改目标对象的指定字段值?直接通过对象去设置或者直接修改代码设置字段值不香吗?
答:

  1. 有的时候,我们需要修改一个成员变量的值,但是该成员在jar包中或者在其他位置且没有提供对应的修改方法或者成员是私有的,导致我们不能从代码层面修改。
  2. 有的时候,代码是别人提供的,并且成员定义成了private私有的且没有提供setter方法,而公司规则不允许直接修改别人提供的代码。

基于这些常见的原因,因此可能需要我们通过非常规手段去修改目标对象的指定成员值。
使用反射修改目标类字段的核心API是Field类的set()方法:

public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException;  

这个方法我们需要注意的是:如果底层需要设置的字段是static的话,则第一个参数可以忽略,直接传null值即可。
这个特性很重要,意味着我们通过反射设置一个static字段的值的时候,可以不用传递实例对象进去,比如反射设置接口中的常量字段,我们无法传递一个接口实例进去,就可以直接传递null。
实际上对于底层字段是static的,传递一个明确的实例对象进去毫无意义,因为static字段不是实例对象自己的,而是隶属于类。

1.1 使用反射设置普通成员变量的值

对于一个javabean来说,其存在普通的字段。一般来讲,对一个目标对象的指定字段进行值的重置操作的话,存在两种常见的额方式:

  1. 直接反射目标字段,强制赋值;
  2. 如果目标字段有指定的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

解释

  1. 获取所有父类getAllSuperclasses 方法会从目标类开始,沿着继承链不断向上获取所有的父类,直到没有父类为止(即父类为 null,通常是 Object 类)。

  2. 获取所有接口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
珍珠

结果分析:

  1. 编译器进行宏替换的final常量字段,反射无法修改,因为这些常量在编译阶段就明确了,不参与JVM的动态操作。
  2. 编译器不进行宏替换的其他字段,比如普通的成员变量和那些引用类型的常量字段,通过反射可以修改,因为它们需要在JVM中进行符号引用向直接引用的替换操作,因此反射可以操作它们。
  3. 实际上反射修改常量,已经修改成功了,通过调试,可以看到在JVM内存中目标字段的值已经是修改后的;只是对于那些编译阶段明确字面量的常量,编译器进行宏替换优化了,没有使用反射修改后的常量值而已。

所以,我们在通过反射修改目标字段值时,需要记住:
java编译器本身对基本类型和String的final常量进行了优化操作(宏替换),目的是提升运行效率。
但从表现上看,这种优化实际上会导致反射此常量时给人反射失效的错觉。
这个问题无解。

1.4 使用反射修改static成员变量

  1. static修饰成员变量后,该成员变量初始化早;随着目标类的装载只初始化一次。
  2. 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成员变量-改造

改造上面案例:

  1. 获取指定的static final的字段Field对象,并且设置为可访问。
  2. 通过反射,去除指定字段的final修饰符。
  3. 成功修改指定final static字段的值。
  4. 修改完成后再通过反射将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 为什么使用反射实例化目标对象?

  1. 当目标类的构造方法是私有的,最典型的就是单例模式中,这时候外部无法创建目标类的对象。
  2. 目标类通过多态,采用某种通用的形式统一接收,这时候想要统一的创建目标类的对象的话,就采用反射统一创建即可。

3.2 使用Class的newInstance()方法创建目标实例对象

观察Class类的newInstance()方法的API解释:

  1. newInstance()方法用于创建当前class对象描述的类的一个实例化对象。
  2. 其默认调用当前Class的public的空构造方法去创建实例,如果当前类没有加载,则触发JVM加载该类。
  3. 如果当前Class没有空构造方法或者空构造方法是不可访问的,将抛出异常。
  4. 如果当前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()创建目标类实例对象

  1. 获取到目标类的Constructor对象,即获取目标类指定的构造器描述对象。
  2. 通过该构造器描述对象,调用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 = 珍珠

文档信息

Search

    Table of Contents