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的值啊。