技术探究:
被final修饰的 变量可以被修改吗 ?
在java的学习中,从刚接触final这个关键词时,就被告知:
------“被final修饰的变量只能一次赋值,以后不能被修改,即常量“。
对于这种比较基础的知识,我对老师的说法还是深信不疑的,总觉得这么基础的东西,作为老师不应该教错吧。而且日常工作里也确实是这么用的,使用final修饰变量作为常量,然后直接在其他类中使用的例子也比比皆是。
但最近在偶然的机会下,跟朋友深入探究了一下关于“被final修饰后的变量是否可以被修改”,才发现里面确实是有猫腻的 ,感觉被各种坑了!!!
首先,先说一下结论。被final修饰的变量中,有一部分变量是可以被修改的!!!
技术分析:
不谈理论,咱们先直接上代码瞅一眼:
首先:我们准备一个空的实体类ObjectOV:
public class ObjectVO {
}
然后,我们再准备一个实体类,用来放用final修饰的各种变量。
public class FinalVO {
private final int intValue = 1;
private final Integer integerValue = new Integer(1);
private final int intNoValue;
private final String stringValue = "1";
private final String newStringValue = new String("1");
private final String stringNoNValue;
private final ObjectVO objectVO = new ObjectVO();
public int getIntValue() {
return intValue;
}
public Integer getIntegerValue() {
return integerValue;
}
public int getIntNoValue() {
return intNoValue;
}
public String getStringValue() {
return stringValue;
}
public String getNewStringValue() {
return newStringValue;
}
public String getStringNoNValue() {
return stringNoNValue;
}
@Override
public String toString() {
return "FinalVO{" +
"intValue=" + intValue +
", integerValue=" + integerValue +
", intNoValue=" + intNoValue +
", stringValue='" + stringValue + '\'' +
", newStringValue='" + newStringValue + '\'' +
", stringNoNValue='" + stringNoNValue + '\'' +
", objectVO=" + objectVO +
'}';
}
public FinalVO(int intNoValue, String stringNoNValue) {
this.intNoValue = intNoValue;
this.stringNoNValue = stringNoNValue;
}
}
上面这个实体类中,所有变量都被final修饰。变量类型包括-----已赋值的基础变量、已赋值的包装类变量、未赋值的基础变量、直接赋值的String类型、new String方式赋值的String类型、未赋值的String类型,还有一个对象。
最后,我们再创建一个测试类,把所有被final修饰的内容都修改一下,然后再来看看结果:
public class FinalTest {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
FinalVO finalVO = new FinalVO(1,"1");
Class clz = FinalVO.class;
Field field = null;
System.out.println(finalVO.toString());
modifyValue(field,clz,finalVO,"intValue",2);
modifyValue(field,clz,finalVO,"integerValue",2);
modifyValue(field,clz,finalVO,"intNoValue",2);
modifyValue(field,clz,finalVO,"stringValue","2");
modifyValue(field,clz,finalVO,"newStringValue","2");
modifyValue(field,clz,finalVO,"stringNoNValue","2");
modifyValue(field,clz,finalVO,"objectVO",new ObjectVO());
System.out.println(finalVO.toString());
}
private static void modifyValue(Field field,Class clz, FinalVO finalVO, String fieldName, Object obj) throws NoSuchFieldException, IllegalAccessException {
field = clz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(finalVO,obj);
}
}
接下来就是见证结果的时刻了:
FinalVO{intValue=1, integerValue=1, intNoValue=1, stringValue='1', newStringValue='1', stringNoNValue='1', objectVO=com.company.finalTest.ObjectVO@4b67cf4d}
FinalVO{intValue=1, integerValue=2, intNoValue=2, stringValue='1', newStringValue='2', stringNoNValue='2', objectVO=com.company.finalTest.ObjectVO@12a3a380}
让我们来分析一下这个结果:
从结果可以看到,只有被初始赋值的基础类型和被初始直接赋值的String类型的值没有改变,其余被final修改的内容都已出现变化。
技术总结:
让我们来分析一下上面这个结果:
对于这个结果,我一开始也不是太明白,然后开始大量的搜原因,最后得出来个名词—“内联函数“。
内联函数,编译器将指定的函数体插入并取代每一处调用该函数的地方(上下文),从而节省了每次调用函数带来的额外时间开支。
我们可以理解成:JVM再编译代码的时候,会对代码进行一些优化。比如这段代码中的获取变量值,其实在编译阶段就已经被直接赋值好了。
用上面代码举例:
public int getIntValue() {
return intValue;
}
这段代码经过JVM的内联优化之后会变成(可以通过反编译查看):
public int getIntValue() {
return 1;
}
由此可见,我们可以修改对应变量的值,但无论我们怎么修改,在获取这个int值的时候都不会获取到修改后的值。
让我们来总结一下结果:
被final修饰的部分变量是可以被修改的,其中被初始赋值的基础类型和被初始直接赋值的String类型,这两种会被JVM进行内联优化 ,无法通过对象获取修改后的值。
期间,还发现了一个变量--StringBugger:
String是字符串变量,它的对象是可以扩充和修改的,即使是被final修饰,依然可以修改。
public class Test02 {
public static void main(String[] args) {
final StringBuffer sb = new StringBuffer("sb");
System.out.println(sb);
sb.append("a");
System.out.println(sb);
}
}
结果是:
sb
sba