问:下面程序段中几个 invokeX 方法在运行时哪些可以达到预期效果?哪些不能?为什么?
public class Test {
private static final Integer KEY_EXIT = 1024;
private static void invok1() throws NoSuchFieldException, IllegalAccessException {
System.out.println("invok1->"+Test.KEY_EXIT);
Field field = Test.class.getDeclaredField("KEY_EXIT");
field.setAccessible(true);
field.set(null, 1000);
System.out.println("invok1-<"+Test.KEY_EXIT);
}
private static void invok2() throws NoSuchFieldException, IllegalAccessException {
System.out.println("invok2->"+Test.KEY_EXIT);
Field field = Test.class.getField("KEY_EXIT");
field.set(null, 512);
System.out.println("invok2-<"+Test.KEY_EXIT);
}
private static void invok3() throws NoSuchFieldException, IllegalAccessException {
System.out.println("invok3->"+Test.KEY_EXIT);
Field field = Test.class.getDeclaredField("KEY_EXIT");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, 256);
System.out.println("invok3-<"+Test.KEY_EXIT);
}
public static void main(String[] args) throws Exception {
invok1();
invok2();
invok3();
}
}
答:上面程序运行结果如下。
//invok1运行结果
invok1->1024
Exception in thread "main" java.lang.IllegalAccessException: Can not set static final java.lang.Integer field Test.KEY_EXIT to java.lang.Integer
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 Test.invok1(Test.java:14)
at Test.main(Test.java:37)
//invok2运行结果
invok2->1024
Exception in thread "main" java.lang.NoSuchFieldException: KEY_EXIT
at java.lang.Class.getField(Class.java:1703)
at Test.invok2(Test.java:20)
at Test.main(Test.java:38)
//invok3运行结果
invok3->1024
invok3-<256
对于 invok1 方法来说成功获取了
KEY_EXIT
静态常量,但是由于是 final 常量的,不允许直接修改,所以直接调用 set 时发生修改异常。对于 invok2 方法来说
getFields()
方法只能获得某个类及其父类中的所有的 public 字段,而getDeclaredFields()
方法却能获得某个类(不包括父类)的所有字段(包括 public、private、proteced 等。同样类似的还有getConstructors()
和getDeclaredConstructors()、getMethods()
和getDeclaredMethods()
方法,所以 invok2 会提示找不到字段而崩溃。对于 invok3 来说就是标准的反射修改静态常量操作。
问:下面程序段中几个 invokeX 方法的运行结果是什么?
public class Test {
private static final int KEY_EXIT = 1024;
private static int KEY_BACK = 1025;
private static final String KEY_STR = "1001";
private static void invok1() throws NoSuchFieldException, IllegalAccessException {
System.out.println("invok1->"+Test.KEY_EXIT);
Field field = Test.class.getDeclaredField("KEY_EXIT");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, 256);
System.out.println("invok1-<"+Test.KEY_EXIT);
}
private static void invok2() throws NoSuchFieldException, IllegalAccessException {
System.out.println("invok2->"+Test.KEY_BACK);
Field field = Test.class.getDeclaredField("KEY_BACK");
field.setAccessible(true);
field.set(null, 1000);
System.out.println("invok2-<"+Test.KEY_BACK);
}
private static void invok3() throws NoSuchFieldException, IllegalAccessException {
System.out.println("invok3->"+Test.KEY_STR);
Field field = Test.class.getDeclaredField("KEY_STR");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, "512");
System.out.println("invok3-<"+Test.KEY_STR);
}
public static void main(String[] args) throws Exception {
invok1();
invok2();
invok3();
}
}
答:上面程序运行结果如下。
//invok1运行结果
invok1->1024
invok1-<1024
//invok2运行结果
invok2->1025
invok2-<1000
//invok3运行结果
invok3->1001
invok3-<1001
有趣的事情发生了。你可能会去 debug 断点调试 invok1 和 invok3,你会发现相关成员属性都得到了正确的反射修改值,但是为什么输出却不是反射修改后的值?
其实原因很简单,当我们定义基本类型的 final 常量或者 String 类型的 final 常量时(只要为 final,不限制有无 static),如果在编译时能确定其确切值则编译器会将其用到的地方用其实际值进行替换,譬如
static final int A = 23;
println(A);
if(i > A) {}
这样的语句会被编译成
static final int A = 23;
println(23);
if(i > 23) {}
的形式,所以即便运行时反射成功也没有任何意义,因为相关值已经在编译时被替换为了常量,而对于包装类型则没事。
所以在反射 final 修饰的变量时一定要留意其是否会在编译时被替换的问题,否则不但不会报错还会给自己带来不必要的错觉麻烦。