final局部变量也可称为局部常量,赋值以后不再改变。
Java内部类是位于类内部的类,方法内部类即位于类方法内部的类。
比如,Inner是Outer的foo()方法内部类:
public class Outer { public Other foo() { final int a = 123; final String str = "hello"; final Other other = new Other(); final class Inner extends Other { @Override public void bar() { int a1 = a; String str1 = str; Other other1 = other; } } return new Inner(); } public static void main(String[] args) { Other other = new Outer().foo(); other.bar(); }}class Other { public void bar() {}}
Java语言规范中说方法内部类只能访问该方法的final局部变量:
Any local variable, formal parameter, or exception parameter used but not declared in an inner class must be declared final.
并且,局部变量需要在内部类定义之前赋值:
Any local variable used but not declared in an inner class must be definitely assigned before the body of the inner class.
之所以这么规定,一个理由是:方法执行结束,方法内的局部变量已经销毁,而方法内部类的对象仍可能存在。比如上例中,other.bar()执行时,foo()内的局部变量已经出栈销毁。
问题是,为什么在bar()中能够访问foo()中本该已经销毁了的final局部变量呢?
利用javap得到Outer$1Inner.class的字节码:
public void bar(); Code: Stack=1, Locals=4, Args_size=1 0: bipush 123 2: istore_1 3: ldc #24; //String hello 5: astore_2 6: aload_0 7: getfield #14; //Field val$other:LOther; 10: astore_3 11: return LineNumberTable: line 12: 0 line 13: 3 line 14: 6 line 15: 11
在foo()方法中定义的3个final局部变量:a,str和other,在Inner的bar()方法中分别被替换成了字面量和Outer$1Inner.class常量池中的常量。
const #14 = Field #1.#15; // Outer$1Inner.val$other:LOther;const #15 = NameAndType #7:#8;// val$other:LOther;const #16 = Method #3.#17; // Other."":()Vconst #17 = NameAndType #9:#18;// " ":()Vconst #18 = Asciz ()V;const #19 = Asciz LineNumberTable;const #20 = Asciz LocalVariableTable;const #21 = Asciz this;const #22 = Asciz LOuter$1Inner;;const #23 = Asciz bar;const #24 = String #25; // hello
由于final局部变量在内部类定义之前赋了值,且不可变,在编译时即可生成该局部变量的常量副本。
虽然方法局部变量的本体已经销毁,它们的静态副本却保留了下来,供方法内部类的对象在合适的时候调用。也可以理解为,局部变量的生命周期通过副本的形式延长了。