zoukankan      html  css  js  c++  java
  • 利用反射修改final数据域

    当final修饰一个数据域时,意义是声明该数据域是最终的,不可修改的。常见的使用场景就是eclipse自动生成的serialVersionUID一般都是final的。

    另外还可以构造线程安全(thread safe)的immutable类,比如String,其数据域都是final的。这些使用场景都建立在final不可修改这个条件上,但是,反射可以打破这一切。

    1.利用反射修改final数据域

    首先,构造一个Person类,里面有个final字段NAME。我们尝试着修改这个字段。顺利的出乎意料。

    public class Person {
    
        public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
            Person p = new Person();
            Field field = p.getClass().getDeclaredField("NAME");
            field.setAccessible(true);
            field.set(p,"Hello");
            System.out.println(field.get(p));
            //p.printName();
        }
    
        private final String NAME = "Clive";
        public Person() {
    
        }
        public void printName() {
            System.out.println(NAME);
        }
    }
    /***************
    console print:
    Hello
    ***************/

    2.内联与内联消除

    NAME数据域如此简单的就被修改了,final真是太"不安全了"! 但是,当我们调用p.printName() 时,控制台打印的却是"Clive"字符串。这是因为JVM做了优化处理, 当一个数据域被final修饰,那就表明这个数据域是常量,JVM会把所有NAME数据域出现的地方全部用"Clive"替换掉, 比如 printName() 方法其实被优化成了这样。

    public void printName() { System.out.println("Clive"); }

    所以,要想不被自动优化,就要把代码弄得复杂点,如下

    public class Person {
    
        public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
            Person p = new Person();
            Field field = p.getClass().getDeclaredField("NAME");
            field.setAccessible(true);
            field.set(p,"Hello");
            System.out.println(field.get(p));
            p.printName();
        }
    
        private final String NAME =(null!=null?"Clive":"Clive"); //声明时即初始化
        public Person() {
            //或者,在这里设置NAME数据域的值
            //NAME="Clive";
        }
        public void printName() {
            System.out.println(NAME);
        }
    }
    /***************
    console print:
    Hello
    Hello
    ***************/

    结果见 console print,顺利消除了优化,final字段最终被修改了!

    3.修改static final数据域

    如果在NAME字段再增加一个static关键字修饰,然后再用反射修改的话就不行了, 会抛出异常

    java.lang.IllegalAccessException: Can not set static final int field ...

    这时,修改Field中的modifiers数据域,清除代表final的那个bit,才可以成功修改。

    public class Person {
    
        public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
            Person p = new Person();
            Field field = p.getClass().getDeclaredField("NAME");
            Field modifiers = field.getClass().getDeclaredField("modifiers");
            modifiers.setAccessible(true);
            modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);//fianl标志位置0
            field.set(p,"Hello");
            System.out.println(field.get(p));
            p.printName();
        }
    
        private final String NAME =(null!=null?"Clive":"Clive");
        public Person() {
        }
        public void printName() {
            System.out.println(NAME);
        }
    }
    /**************
    console print:
    Hello
    Hello
    **************/

    总结

    这个知识点感觉知道就好,平时还是不要修改final数据域的好 :)

    引用

    1.https://www.oschina.net/question/1245392_159103

    2.https://github.com/jOOQ/jOOR

  • 相关阅读:
    线程的几种状态
    NSThread&线程间通信
    大文件下载--断点续传--NSURLConnection
    NSURLConnection及NSURLConnectionDataDelegate
    单例实现 CGD与条件编译实现单例类
    单例模式 饿汉式 ARC
    Load与initialize方法
    单例模式 (懒汉式)ARC
    sqlserver 分页查询总结
    .net 弹窗方式
  • 原文地址:https://www.cnblogs.com/fudashi/p/6624379.html
Copyright © 2011-2022 走看看