cover_image

Java 反射常量时一道非常经典有名的面试题

工匠若水 码农每日一题
2018年04月24日 23:37

图片

码农每日一题
长按关注,工作日每天分享一个技术知识点。
图片

问:下面程序段中几个 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 修饰的变量时一定要留意其是否会在编译时被替换的问题,否则不但不会报错还会给自己带来不必要的错觉麻烦。当然除过基本类型的 final 外 String 类型的 final 为啥会有类似特性的问题可以参看本号 String 专题历史推送或者在本号搜索中查询 String 关键字了解更多。

这道经典题在爆栈上面也有讨论,参见 https://stackoverflow.com/questions/1615163/modifying-final-fields-in-java

图片更多反射循序渐进深入题目请关注下次推送,本号主打短小精干~

Java 反射相关基础面试题扫荡

反射之数组参数类型相关的坑爹面试题

反射之基本数据类型相关踩坑笔试题

图片

图片

图片看完不过瘾?看完还想看?那就点击左下角阅读原文查看本号历史经典技术知识点题目推送,解锁更多基础知识~

继续滑动看下一个
码农每日一题
向上滑动看下一个