首先要明确一点,如果变量在方法外面,是不用加final的,比如:
class Outer{
int num=3;
void method(){
class Inner{
void show(){
System.out.println("show.."+num);
}
}
Inner in=new Inner();
in.show();
}
}
class Test{
public static void main(String[]args){
//输出show..3
new Outer().method();
}
}
在JDK1.8以前,如果变量定义在方法内部,或者作为方法的形参,必须加final,这是为什么呢?
class Outer{
void method(){
//定义在内部,或者在形参
int num=3;
class Inner{
void show(){
System.out.println("show.."+num);
}
}
Inner in=new Inner();
in.show();
}
}
class Test{
public static void main(String[]args){
//编译失败
//Cannot refer to a non-final variable num inside an inner class defined in a different method
new Outer().method();
}
}
根本原因是方法内的局部变量和实例对象的生存周期不一样罢了。
我们知道Java把对象实例放在堆内存中,把方法的局部变量放在栈内存中,下面我们就从JVM的角度分析一下这个过程。
main方法进栈,遇到Outer这个类,首先加载这个类到方法区,然后在堆内存中创建Outer的对象。
接下来,method方法进栈,在栈帧中创建局部变量num,接着又遇到Inner这个类,加载Inner(包括show方法)到方法区。
接着,在堆内存中创建Inner实例对象,show方法进栈,show方法要去调用method栈帧里的num。OK,此时method没有出栈,自然可以调用到。
但是一旦show出栈,那么method也出栈,此时局部变量num就消亡了。而Inner对象in还在堆内存逍遥着呢(JVM不会立刻回收对象),手里还拽着一个根本就不存在的变量呢,这一下不就矛盾了吗。
所以,解决的方法,就是给这个局部变量加上final修饰符,让它成为一个常量,跑到方法区的常量池躺着,你Inner要用,直接拿就行,不用担心变量出栈突然死亡。
JDK1.8中,不加final也能编译通过,实际上是编译的时候自动加上了final。
如果尝试修改num的值,还是会报错。