问题描述
在我的一些单元测试中,我遇到了在最终静态字段上反射的奇怪行为.以下是说明我的问题的示例.
I have been faced in some of my Unit test with a strange behaviour with Reflection on final static field. Below is an example illustrating my issue.
我有一个包含整数的基本单例类
I have a basic Singleton class that holds an Integer
public class BasicHolder {
private static BasicHolder instance = new BasicHolder();
public static BasicHolder getInstance() {
return instance;
}
private BasicHolder() {
}
private final static Integer VALUE = new Integer(0);
public Integer getVALUE() {
return VALUE;
}
}
我的测试用例循环并通过反射将 VALUE 设置为迭代索引,然后断言 VALUE 正确地等于迭代索引.
My test case is looping and setting through Reflection the VALUE to the iteration index and then asserting that the VALUE is rightfully equal to the iteration index.
class TestStaticLimits {
private static final Integer NB_ITERATION = 10_000;
@Test
void testStaticLimit() {
for (Integer i = 0; i < NB_ITERATION; i++) {
setStaticFieldValue(BasicHolder.class, "VALUE", i);
Assertions.assertEquals(i, BasicHolder.getInstance().getVALUE(), "REFLECTION DID NOT WORK for iteration "+i);
System.out.println("iter " + i + " ok" );
}
}
private static void setStaticFieldValue(final Class obj, final String fieldName, final Object fieldValue) {
try {
final Field field = obj.getDeclaredField(fieldName);
field.setAccessible(true);
final Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, fieldValue);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException("Error while setting field [" + fieldName + "] on object " + obj + " Message " + e.getMessage(), e);
}
}
}
结果非常令人惊讶,因为它不是恒定的,我的测试在大约 1000 次迭代时失败,但似乎永远不会相同.
The result is quite surprising because it's not constant,my test fails around iteration ~1000 but it never seems to be always the same.
有人遇到过这个问题吗?
Anyone has already faced this issue ?
推荐答案
JLS 提到在构造后修改 final 字段是有问题的 - 见17.5.最终字段语义
The JLS mentions that modifying final fields after construction is problematic - see17.5. final Field Semantics
声明为 final 的字段被初始化一次,但在正常情况下永远不会改变.final 字段的详细语义与普通字段的语义有些不同.特别是,编译器有很大的自由来跨越同步障碍和对任意或未知方法的调用移动最终字段的读取.相应地,允许编译器将最终字段的值缓存在寄存器中,并且在必须重新加载非最终字段的情况下,不会从内存中重新加载它.
另一个问题是规范允许对最终字段进行积极优化.在一个线程中,允许对 final 字段的读取重新排序,并使用那些未在构造函数中发生的对 final 字段的修改.
除此之外,Field.set 的 JavaDocs 也包含关于此的警告:
In addition to that, the JavaDocs of Field.set also include a warning about this:
以这种方式设置 final 字段仅在具有空白 final 字段的类实例的反序列化或重建期间有意义,然后它们才能被程序的其他部分访问.在任何其他上下文中使用可能会产生不可预测的影响,包括程序的其他部分继续使用该字段的原始值的情况.
我们在这里看到的似乎是 JIT 利用了语言规范授予的重新排序和缓存可能性.
It seems that what we are witnessing here is the JIT taking advantage of the reordering and caching possibilities granted by the Language Specification.
这篇关于使用反射覆盖最终静态字段是否有限制?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!