【反射】反射获取私有字段小记

发布时间 2023-12-29 13:56:04作者: onejay

问题:

// 直接按类字面量获取
Class<?> myClass = ClassTestA.class;
// 全类名反射获取
Class<?> myClass = Class.forName("com.cambrianwenjie.demo.ClassTestA");

// 获取私有字段
Field privateField = myClass.getDeclaredField("name");

// 设置私有字段可访问
privateField.setAccessible(true);

// 获取并修改私有字段的值
String fieldValue = (String) privateField.get(myClass);

以上代码在运行到获取并修改私有字段的值时,会提示

Exception in thread  "main" java.lang.IllegalArgumentException: Can not set java.lang.String field com.cambrianwenjie.demo.ClassTestA.name to java.lang.Class
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java: 167 )
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java: 171 )
    at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java: 58 )
    at sun.reflect.UnsafeObjectFieldAccessorImpl.get(UnsafeObjectFieldAccessorImpl.java: 36 )
    at java.lang.reflect.Field.get(Field.java: 393 )

 

疑惑:

forName获取到的就是类的全部信息,包括字段(Field)的信息。通过Class对象可以调用Field类中的get方法来获取字段的值,那么为什么会报错?

查看Field的get()方法的JavaDoc

 

大概翻译一下:

  • 返回由该Field对象表示的字段在指定对象上的值。如果字段具有原始类型,则该值会自动包装为对象。
  • 获取底层字段的值的方式如下:
    • 如果底层字段是静态字段,则忽略obj参数;它可以为null。
    • 否则,底层字段是实例字段。如果指定的obj参数为null,则该方法抛出NullPointerException异常。如果指定的对象不是声明底层字段的类或接口的实例,则该方法抛出IllegalArgumentException异常
  • 如果Field对象正在强制执行Java语言的访问控制,并且底层字段不可访问,则该方法抛出IllegalAccessException异常。如果底层字段是静态字段,并且尚未初始化声明该字段的类,则会初始化该类。
  • 否则,从底层实例或静态字段中检索值。如果字段具有原始类型,则在返回之前将该值包装在对象中,否则按原样返回。
  • 如果字段在obj的类型中被隐藏,则根据前面的规则获取字段的值。

问题原因:

关键就在于上面加粗斜体的部分,更关键的是加粗斜体部分的红字

 

类的字面量和全类名反射获取的Class都是类对象而非类的实例对象

此处解释下类对象和类的实例对象:

  1. 定义:

    • 类对象(Class Object):表示整个类的定义,包括类的结构、方法、字段等信息。在Java中,类对象是在运行时由Java虚拟机(JVM)动态创建和管理的。
    • 类的实例对象(Instance Object):表示类的一个具体实例,是类对象的一个实例化对象。在Java中,类的实例对象是通过使用new关键字或其他创建对象的方式创建的。
  2. 内容:

    • 类对象:包含了类的定义和结构信息,例如类的方法、字段、构造函数、静态代码块等。类对象在内存中只有一份,对于一个类而言,类对象是唯一的。
    • 类的实例对象:表示类的一个具体实例,每个实例都有自己的独特状态和数据。每次创建类的实例对象时,都会在内存中分配一块新的内存空间来存储实例的数据。
  3. 访问:

    • 类对象:可以通过类字面量(例如ClassName.class)或Class.forName()方法来获取类对象。通过类对象可以获取类的各种信息,如方法、字段、注解等。
    • 类的实例对象:通过创建对象的方式,使用new关键字或其他创建对象的方式来获取类的实例对象。通过实例对象可以访问和操作实例的成员变量和方法。
  4. 功能:

    • 类对象:用于获取类的结构信息,进行反射操作,如动态创建对象、调用方法、访问字段等。
    • 类的实例对象:用于表示类的一个具体实例,可以通过实例对象来访问和操作实例的成员变量和方法。

总的来说,类对象是类的定义和结构信息的表示,而类的实例对象是类的具体实例,每个实例有自己的状态和数据。类对象用于操作和获取类的结构信息,而类的实例对象用于访问和操作实例的成员变量和方法。

因此如果要调用Field的get()方法,需要传入一个实例化对象才可以。

比如将上述代码最后一个行改为:

// 获取并修改私有字段的值
String fieldValue = (String) privateField.get(myClass.newInstance());