长大后想做什么?做回小孩!

0%

内部类总结

原理分析:

图片摘自网络,未找到原作者,如需删除请告知

mlQxsJ.md.png

为什么内部类可以直接访问外部类的成员,而外部类不能直接访问内部类?

首先写一个类:

m3CavV.png

编译之后产生两个文件:

m3CwuT.png

用javap反编译Outter$Inner.class:

m3CBbF.md.png

m3C0DU.md.png

答:可以看到,内部类中保存的有外部类的引用,所以内部类可以直接访问外部类的成员。通过同样的方式反编译Outter.class文件后会发现外部类中没有传入内部类的引用。


为什么局部内部类和匿名内部类只能访问被final修饰的局部成员?

:JDK1.8之后虽然不写final编译器并不会报错,但是依然不允许修改被使用的局部成员,相当于隐式的添加了final修饰词。

这是一个编译器设计的问题,从代码中直接看,很容易让人误解为内部类直接使用了局部成员(参数)。实际上并不是这样的。

1
2
3
4
5
6
7
8
9
10
public class Outter {
public void test(final String name,int a){
class Inner{
public void show(){
System.out.println(name);
}
}
new Inner().show();
}
}

可以这么理解:局部有个变量name,当内部类要使用的时候就会使“内部类name”=“局部变量name”,然后去使用这个“内部类name”。这么做很容易出现问题,如果这个“内部类name”改变了,“局部变量name”是不会随着改变的。为了解决这个问题,编译器要求这个被使用的“局部变量name”被final修饰。(被访问的外部类成员不需要final修饰,因为内部类中有外部类的引用,所有的变量修改都会真实的反映到内部类和外部类中

反编译之后,会发现实际上的操作是这样的:

m3tlEF.png

在show()方法之前增加了一个初始化的操作,可以这么理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Outter {
public void test(final String name,int a){
//方法内部类
class Outter$Inner{
//增加初始化方法
public Inner( String Inner$name){
this.Inner$name=name;
}
public void show(){
System.out.println(Inner$name);
}
}
new Outter$Inner().show();
}
}

答:

  1. 保证一致性:在内部类中通过构造器将局部的变量copy了一份,然后使用copy后的变量。实际上内部类中使用的是自身的属性。所以为了保持“本体”和“克隆体”的一致性,必须加上final修饰符。
  2. 改变变量的生命周期:当局部方法结束时,局部变量的生命周期就结束了,但是局部内部类(匿名内部类)对象的生命周期可能还没有结束,这时对象再去使用局部变量就变得不可能了,给局部变量加上final修饰符其实就是改变了它的生命周期。