概念介绍
至于概念的介绍直接上代码,很容易分清三者的区别。
public class Outer {
public class Inner {//内部类
...
}
public static class StaticInner{//静态内部类
...
}
public void method(){
class methodClass{//局部内部类
...
}
}
}
内部类分析
看下面的代码:
public class Outer {
private int outerNumber = 0;
public void outerMethod() {
Inner inner = new Inner();
int a = inner.innerNumber;
}
public class Inner {
private int innerNumber = 1;
public void innerMethod() {
int a = outerNumber;
}
}
public static void main(String[] args) {
}
}
上面的这段代码是可以编译通过的,主要说明了两件事
1、外部类可以访问内部类的私有属性。
2、内部类可以访问外部类的私有属性。
下面我们一一来探讨这两件事是怎么办到的。
内部类如何访问到外部类的私有属性?
学java的时候我们知道一个类不可能访问到另一个类的私有属性,只能访问不同类的共有属性,那为什么内部类就能访问到外部类的私有属性呢?其实这是编译器捣的鬼。在cmd中输入javap -c Outer.class
得到Main.class的反编译代码:
为了形成对比,把内部类中的innerMethod
方法注释掉,再次反编译Outer.class
,得到相应的反编译代码如下:
对比下你会发现当把innerMethod
方法注释掉后,再次反编译Outer.class
,得到的反编译代码也少了一部分,少了的部分如下:
static int access$0(Outer);
Code:
0: aload_0
1: getfield #12 // Field outerNumber:I
4: ireturn
为了方便观察,我把innerMethod
也贴在这:
public void innerMethod() {
int a = outerNumber;//outerNumber是外部类的私有属性。
}
所以看到这,我们可以得出结论,如果内部类需要访问外部类的私有属性,编译器会自动为访问的变量加上静态的access
方法,传入内部类持有的外部类的引用即可访问,那这个外部类的引用在什么地方呢,下文会介绍。
外部类如何访问到内部类的私有属性?
同样的步骤,cmd输入javap -c Outer$Inner.class
得到内部类的反编译代码:
看到了吧,第三行的final Outer this$0
这就是内部类持有的外部类的引用。同样你也会看到熟悉的access
方法,编译器通过出入内部类的引用(这个引用便是在outerMehtod
中new出来的inner
对象)就可以访问到内部类的私有属性了。但是,还不算完,我们仍然需要注意以下几点:
1、当内部类没有访问外部类私有属性的代码的时候,编译器是不会跟我们加入access方法的,同理,当外部类没有访问内部类的私有属性的代码的时候,编译器也不会给我们生成access方法。
2、只有访问private修饰的属性和方法时,编译器才会生成相应的access方法。
3、访问的是私有属性时,access方法的返回值类型为访问的私有属性的类型(上述代码中outerNumber为int类型,因此access方法返回值为int,若outerNumber为double,那么access方法返回值就变成了double),访问的是私有方法时,access方法的返回值类型为要访问的方法的返回值类型。
静态内部类分析
相应代码如下:
public class Outer {
private int outerNumber = 0;
private void outerMethod() {
Inner inner = new Inner();
// inner.innerNumber;//会直接报错。
}
public static class Inner {
private int innerNumber = 1;
public void innerMethod() {
// int a = outerNumber;//会直接报错。
}
}
public static void main(String[] args) {
}
}
我们可以发现,在静态内部类中不能访问外部类的私有属性和方法的,同样外部类中也不能访问静态内部类中的私有属性和方法。
反编译Outer.class
和Outer$Inner.class
,得到如下反编译代码:
比较静态内部类和内部类的反编译代码,很容易发现静态内部类中没有外部类的引用,因此静态内部类想要访问外部类的私有属性和方法就变成了不可能的事情,这也是为什么在Android中为了防止内存泄漏Handler和AsynTask要声明成静态内部类的原因。
局部内部类
代码如下:
public class Outer {
private void outerMethod() {
final int number = 0;
class Inner {
private int innerNumber = 0;
public Inner() {
}
public void innerMethod() {
int a = number;
}
}
Inner inner = new Inner();
}
public static void main(String[] args) {
}
}
对于局部内部类只需要知道两点就好:
1、局部内部类不能带任何修饰符,它对于包围它的方法之外的任何东西都是不可见的,其生命周期仅仅是包含他的方法。
2、局部内部类和匿名类还是有区别的,匿名类没有构造器,它将构造器参数传递给超类构造器。
3、如果需要引用包围它的方法中的属性,此属性必须被final修饰。这是为什么呢?当我们需要在局部内部类里面使用此属性的时候,Inner类其实会多出一个此属性的备份隐藏域,使用的时候其实是使用的这个备份,但是当此属性的值在不停的变化时,备份就需要不断的进行数据的同步,以保持和原属性的值一致,结果可想而知。因此,编译器干脆规定此属性只要被声明了就是final的,不可以再变化。