为什么内部类可以无条件访问外部类成员?
public class Outter {
private Inner inner = null;
public Outter() {
}
}
public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}
protected class Inner {
public Inner() {
}
}
我们通过 javap -v Outter$Inner
来反编译代码,最终得到字节码文件:
{
// 注意这里
final com.cxh.test2.Outter this$0;
public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);
Code:
Stack=2, Locals=2, Args_size=2
0: aload_0
1: aload_1
2: putfield #10; //Field this$0:Lcom/cxh/test2/Outter;
5: aload_0
6: invokespecial #12; //Method java/lang/Object."":()V
9: return
LineNumberTable:
line 16: 0
line 18: 9
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/cxh/test2/Outter$Inner;
}
第一行就明确了一个外部类的引用,看构造方法就更清晰了:传入了一个外部类的引用。由此可以看出,虽然我们在定义的内部类的构造器是无参构造器,编译器还是会默认添加一个参数,该参数的类型为指向外部类对象的一个引用,所以成员内部类中的Outter this&0 指针便指向了外部类对象,因此可以在内部类随意访问外部类的成员。
为什么局部内部类和匿名内部类访问局部变量要用final修饰?
我们先看这样一段代码
public class Test {
public static void main(String[] args) {
}
public void test(final int a) {
new Thread() {
public void run() {
System.out.println(a);
}
}.start();
}
public void test1(final int b) {
final int a = 10;
new Thread() {
public void run() {
System.out.println(a);
System.out.println(b);
}
}.start();
}
}
我们反编译得到的结果是:
Constant pool:
#1 = Methodref #9.#22 // java/lang/Object."":()V
#2 = Class #23 // com/roger/algorithm/Test$1
#3 = Methodref #2.#24 // com/roger/algorithm/Test$1."":(Lcom/roger/algorithm/Test;I)V
#4 = Methodref #2.#25 // com/roger/algorithm/Test$1.start:()V
#5 = Class #26 // com/roger/algorithm/Test$2
#6 = Methodref #5.#24 // com/roger/algorithm/Test$2."":(Lcom/roger/algorithm/Test;I)V
#7 = Methodref #5.#25 // com/roger/algorithm/Test$2.start:()V
#8 = Class #27 // com/roger/algorithm/Test
#9 = Class #28 // java/lang/Object
#10 = Utf8 InnerClasses
public void test(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=4, locals=2, args_size=2
0: new #2 // class com/roger/algorithm/Test$1
3: dup
4: aload_0
5: iload_1
6: invokespecial #3 // Method com/roger/algorithm/Test$1."":(Lcom/roger/algorithm/Test;I)V
9: invokevirtual #4 // Method com/roger/algorithm/Test$1.start:()V
12: return
LineNumberTable:
line 10: 0
line 16: 9
line 17: 12
public void test1(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=4, locals=3, args_size=2
0: new #5 // class com/roger/algorithm/Test$2
3: dup
4: aload_0
5: iload_1
6: invokespecial #6 // Method com/roger/algorithm/Test$2."":(Lcom/roger/algorithm/Test;I)V
9: invokevirtual #7 // Method com/roger/algorithm/Test$2.start:()V
12: return
LineNumberTable:
line 21: 0
line 26: 9
line 27: 12
反编译结果依赖 Java1.8.0_201
,注意最开始的Constant pool
,在内部类的初始化方法声明了外部类引用,构造方法中的引用声明省去了,应该是JVM的一个优化。总的原理与JVM低版本相同:如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。
从上面可以看出,在run方法中访问的变量a根本就不是test方法中的局部变量a。这样一来就解决了前面所说的 生命周期不一致的问题。但是新的问题又来了,既然在run方法中访问的变量a和test方法中的变量a不是同一个变量,当在run方法中改变变量a的值的话,会出现什么情况?
对,会造成数据不一致性,这样就达不到原本的意图和要求。为了解决这个问题,java编译器就限定必须将变量a限制为final变量,不允许对变量a进行更改(对于引用类型的变量,是不允许指向新的对象),这样数据不一致性的问题就得以解决了。
到这里,想必大家应该清楚为何 方法中的局部变量和形参都必须用final进行限定了。
静态内部类有特殊的地方吗?
从前面可以知道,静态内部类是不依赖于外部类的,也就说可以在不创建外部类对象的情况下创建内部类的对象。另外,静态内部类是不持有指向外部类对象的引用的,这个读者可以自己尝试反编译class文件看一下就知道了,是没有Outter this&0引用的。
这篇文章很好,给了我很大的帮助,我把其中的内容手动实现了一遍,感谢原作者。
https://blog.csdn.net/davidluo001/java/article/details/50377919