基于使用Java反射更改私有静态final字段,我尝试设置私有静态final字段。
(我知道这很不礼貌,但这个问题与代码质量无关;是关于Java反射的)
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
class Main {
static class Foo {
private static final int A = 1;
int getA() {
return A;
}
}
public static void main(String[] args) throws Exception {
Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
Field field = Foo.class.getDeclaredField("A");
field.setAccessible(true);
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, 2);
System.out.println(new Foo().getA()); // should print 2
}
}
这个打印1
我已经在OpenJDK 6和7以及Oracle 7中尝试过了。
我不知道Java反射给出了什么保证。但是如果它失败了,我认为会有一个Exception
(实际上所有的反射方法都会抛出异常)。
这是怎么回事?
Java内联final
字段,这些字段在编译时初始化为常量表达式。
根据Java语言规范,任何
static final
*字段初始化为可以在编译时求值的表达式,必须编译成"内联"字段值的字节码。也就是说,在Main
类内部不会出现动态链接,告诉它在运行时从InterfaceA
中获取A
的值。
反编译字节码,你会发现getA()
的主体只是推入常量1
并返回它。
* - JavaWorld引用说static final
。Kumar指出,在常量变量的定义中,语言规范并不需要static
。我认为Kumar是对的,JavaWorld是错误的。
在某些情况下,java不内联静态final常量。并且不同的Java版本之间的反射行为是不同的(例如在现代Java中获取modifiers
字段将失败),但是有些东西在Java版本之间保持一致- sun.misc.Unsafe
。
您可以使用它来修改任何字段的值,并绕过所有的访问修饰符。
public static void setStaticObjectUnsafe(final Field field, Object value) {
final Object staticFieldBase = theUnsafe.staticFieldBase(field);
final long staticFieldOffset = theUnsafe.staticFieldOffset(field);
theUnsafe.putObject(staticFieldBase, staticFieldOffset, value);
}
对于原语,它是相同的,除了你需要替换value
参数和putObject与它的原语类型的替代:
public static void setStaticIntegerUnsafe(final Field field, int value) {
final Object staticFieldBase = theUnsafe.staticFieldBase(field);
final long staticFieldOffset = theUnsafe.staticFieldOffset(field);
theUnsafe.putInt(staticFieldBase, staticFieldOffset, value);
}
要获得不安全的实例,请参阅此解决方案。
如果你使用的是Eclipse IDE,你需要在首选项中调整编译器设置:Java→编译器→错误/警告→设置"禁止引用(访问规则)";忽略