为什么内部类只能访问finally修饰的外部局部变量?
首先要明确的是:内部类和外部类是处于同一个级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁。
public class Test{
public void test(final int b){
final int a = 10 ;
new Thread(){
public void run(){
Thread.sleep(1000);
System.out.println(a);
System.out.println(b);
}
};
}
}
上面这个案例显而易见,test方法的生命周期和方法内部类的生命周期不同。
而形参局部变量和方法局部变量是不可能脱离方法而单独存在的,也就是说方法结束,变量即销毁,但是内部类对象可能还存在(只是没有人再引用它时,才会死亡)。这里就出现了一个矛盾:内部类对象访问了一个不存在的变量。
为了解决这个问题,内部类中为外部局部变量创建副本,在内部类中使用的“所谓外部局部变量”仅仅是“真实外部局部变量”的一个拷贝,因此,在内部类中为变量重新赋值不会影响外部变量。
反编译Test,深入了解"拷贝":pipush(10)
Microsoft Windows [版本 10.0.18362.720]
(c) 2019 Microsoft Corporation。保留所有权利。
C:\Users\zc980807>javac C:\Users\zc980807\Desktop\Test.java //会产生两个class文件
C:\Users\zc980807>javap -v C:\Users\zc980807\Desktop\Test$1.class
Classfile /C:/Users/zc980807/Desktop/Test$1.class
Last modified 2020-3-22; size 579 bytes
MD5 checksum 236e5ca3975e7b32a6d02109a715c742
Compiled from "Test.java"
class com.huike.Test$1 extends java.lang.Thread
minor version: 0
major version: 52
flags: ACC_SUPER
Constant pool:
#1 = Fieldref #6.#23 // com/huike/Test$1.this$0:Lcom/huike/Test;
#2 = Fieldref #6.#24 // com/huike/Test$1.val$b:I
#3 = Methodref #7.#25 // java/lang/Thread."<init>":()V
#4 = Fieldref #26.#27 // java/lang/System.out:Ljava/io/PrintStream;
#5 = Methodref #28.#29 // java/io/PrintStream.println:(I)V
#6 = Class #30 // com/huike/Test$1
#7 = Class #32 // java/lang/Thread
#8 = Utf8 val$b
#9 = Utf8 I
#10 = Utf8 this$0
#11 = Utf8 Lcom/huike/Test;
#12 = Utf8 <init>
#13 = Utf8 (Lcom/huike/Test;I)V
#14 = Utf8 Code
#15 = Utf8 LineNumberTable
#16 = Utf8 run
#17 = Utf8 ()V
#18 = Utf8 SourceFile
#19 = Utf8 Test.java
#20 = Utf8 EnclosingMethod
#21 = Class #33 // com/huike/Test
#22 = NameAndType #34:#35 // test:(I)V
#23 = NameAndType #10:#11 // this$0:Lcom/huike/Test;
#24 = NameAndType #8:#9 // val$b:I
#25 = NameAndType #12:#17 // "<init>":()V
#26 = Class #36 // java/lang/System
#27 = NameAndType #37:#38 // out:Ljava/io/PrintStream;
#28 = Class #39 // java/io/PrintStream
#29 = NameAndType #40:#35 // println:(I)V
#30 = Utf8 com/huike/Test$1
#31 = Utf8 InnerClasses
#32 = Utf8 java/lang/Thread
#33 = Utf8 com/huike/Test
#34 = Utf8 test
#35 = Utf8 (I)V
#36 = Utf8 java/lang/System
#37 = Utf8 out
#38 = Utf8 Ljava/io/PrintStream;
#39 = Utf8 java/io/PrintStream
#40 = Utf8 println
{
final int val$b;
descriptor: I
flags: ACC_FINAL, ACC_SYNTHETIC
final com.huike.Test this$0;
descriptor: Lcom/huike/Test;
flags: ACC_FINAL, ACC_SYNTHETIC
com.huike.Test$1(com.huike.Test, int);
descriptor: (Lcom/huike/Test;I)V
flags:
Code:
stack=2, locals=3, args_size=3
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:Lcom/huike/Test;
5: aload_0
6: iload_2
7: putfield #2 // Field val$b:I
10: aload_0
11: invokespecial #3 // Method java/lang/Thread."<init>":()V
14: return
LineNumberTable:
line 7: 0
public void run();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: bipush 10 //----------------------------------->这个即为拷贝
5: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
8: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
11: aload_0
12: getfield #2 // Field val$b:I
15: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
18: return
LineNumberTable:
line 9: 0
line 10: 8
line 11: 18
}
SourceFile: "Test.java"
EnclosingMethod: #21.#22 // com.huike.Test.test
InnerClasses:
#6; //class com/huike/Test$1
但是重新赋值导致的问题是:内外两个变量将不再同步。
为了同步,所以要求该变量必须有 final
修饰,使其不可变,这样就保证了内部类的成员变量和方法的局部变量的一致性。