假设我有以下课程:
public class SomeClass {
private final int num;
public SomeClass(int num) {
this.num = num;
}
public int getNum() {
return num;
}
}
当我执行this code来设置num
字段时,一切都很好:
SomeClass obj = new SomeClass(0);
final Field field = SomeClass.class.getDeclaredField("num");
field.setAccessible(true);
Field modField = Field.class.getDeclaredField("modifiers");
modField.setAccessible(true);
modField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(obj, 1);
System.out.println(obj.getNum()); // Prints 1 instead of the initial value 0.
但是,当我从SomeClass
中删除构造函数时,这不再起作用,并且println
语句打印0。
谁能解释这种行为?
让我们看看用于doc方法的Java Field.set
:
如果基础字段是final,则该方法抛出IllegalAccessException,除非此Field对象的setAccessible(true)成功并且该字段是非静态的。以这种方式设置最终字段仅在反序列化或重建具有空白最终字段的类的实例期间才有意义,然后才能使程序的其他部分访问它们。在任何其他上下文中使用可能具有不可预测的影响,包括程序的其他部分继续使用该字段的原始值的情况。
这意味着在您的示例中,如果删除构造函数,则需要将最终字段初始化为某个值,从而使其不为空。在这种情况下,如果使用反射更改最终字段,则可能会产生不可预测的影响,包括程序的其他部分继续使用此字段的原始值的情况。
首先,请注意,即使字段为public
,您也会得到相同的行为,在这种情况下,您不需要设置field
可访问:
final Field field = SomeClass.class.getDeclaredField("num");
Field modField = Field.class.getDeclaredField("modifiers");
modField.setAccessible(true);
modField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(obj, 1);
System.out.println(obj.num);
这是Java编译器优化的结果:实际字段num
设置为1
,但getNum
忽略该值,因为编译器认为它是final
。
即使删除了构造函数(1
),此行也会打印demo:
System.out.println(field.get(obj));
Java编译器注意到final int num
从未在其初始化器之外分配,并将return num
替换为返回num
的初始值。
注意:您的实验提供了一个很好的理由,说明为什么不应该尝试修改您声明为不可修改的字段。