java之一个final引起的反射问题

java中的关键字final关键字标识编译时常量。

对于基本类型,标识其值不可被改变,对于引用类型,标识其引用不可以被改变,但是引用对象的值可以发生变化。

这里的引用类型是包含装箱的类型的,只是装箱类型的封装了一个基本类型的变量,导致现象是装箱类型的也是不可改变引用的,但其实是因为其内部持有的基本类型,并且此基本类型使用final约束了。

我们都知道如果引用类型被final修饰,那么意味着不可以再次进行赋值,并且这个是编译时就确定的。但我们就是想试一试能不能改变final修饰的基本类型以及包装类型?答案当然是可以改啦,只要记住一点unsafe是无所不能的,就可以了。

例如:

public class FinalTest {

    public static void main(String[] args) throws Exception {
        User user = new User();
        user.i = 1; // 这里会报错,final不可以被赋值为1
        }

    public static class User {
        public final Integer i = null;
        public User() {
        }
    }
}

但是我们可以通过反射改:

public class FinalTest {

    public static void main(String[] args) throws Exception {
        User user = new User();
        Field[] fields = user.getClass().getFields();
        fields[0].setAccessible(true);
        fields[0].set(user, 1); // 这样是ok的,改成功了, 这里无论i的类型是什么(无论是包装类,还是基本类型),都是可以改的,但是要注意一点的是,由于编译阶段代码优化问题,直接使用sout是看不出变化的,因此建议打断点调试。
    }

    public static class User {
        public final Integer i = null;
        public User() {
        }
    }
}

同时static final修饰的对象,也可以改变:

public class FinalTest {

    public static void main(String[] args) throws Exception {

        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
        User user = new User();
        System.out.println(User.i);
        Field[] fields = user.getClass().getFields();
        long l = unsafe.staticFieldOffset(fields[0]);
        unsafe.getAndSetObject(User.class, l, 123); // 这样直接把类信息改了
        System.out.println(User.i);
    }

    public static class User {
        public static final Integer i = 10;

        public User() {
        }
    }
}

其实反射也是基于unsafe做的,这里只是探究一下final关键字修饰的变量是否可以更改,大家可不要随意在正式项目中使用反射或者unsafe去改final或者static final的值啊。

发布了29 篇原创文章 · 获赞 2 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/u012803274/article/details/100159630