https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html
嵌套类
java允许使用者在一个类里定义另外一个类,这样的类称之为嵌套类,比如像这样
class OuterClass{
...
class NestedClass{
...
}
}
嵌套类可以分为两种:静态的和非静态的。被声明为静态的嵌套类称之为静态嵌套类(static nested classes),非静态的嵌套类称之为内部类(inner classes)
嵌套类是外部类的成员,其中内部类能够使用外部类的所有成员(包括方法和属性,包括私有)。静态嵌套类不能使用外部类的成员。
为什么要用嵌套类呢?
使用嵌套类主要有这三个理由:
1.如果一个类A只被另一个类B使用,那就可以将A写成B的内部类,这样逻辑上把他们打包成组,也让类所在包结构更合理化
2.假设有两个逻辑上同等级的类A和B,当B需要访问A的私有成员时,就要嵌套
3.将一些小型的类嵌套在使用它们的顶级类中,使代码的可读性和可维护性更强
静态嵌套类
静态嵌套类跟外部类的关系就像静态方法跟静态属性一样,比如静态嵌套类是private的话,其他类就不能用。另外就像类的静态方法一样,静态嵌套类不能直接涉及定义在外部类的实例变量和实例方法,只能通过对象引用来使用它们。
静态嵌套类跟它的外部类实例成员的关系,就像其他顶级类(非嵌套类)一样。实际上,把一个类写成静态嵌套类只能因为package方便
如果说静态内部类跟一般类有什么区别的话就两点:
1.使用时必须带上外部类,比如在实例化的时候
OuterClass.StatiNestedClass nestedObject = new OuterClass.StaticNestedClass();
2.使用外部的静态成员时,语法上可以不带外部类
内部类
就跟实例方法和实例属性一样,内部类能够直接使用外部类对象的属性和方法。同时因为内部类是跟一个实例关联的,它不能定义任何静态成员。(个人认为,这样规定并不是因为不能实现,估计因为当初设计者为了是内部类更像一个成员变量)
内部类实例只能存在于一个外部类实例,并且能直接获取外部类实例的成员
要实例化一个内部类,必须先实例化一个外部类,然后用一下语法来实例化内部类
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
还有两种特殊的内部类:本地类和匿名类 后面介绍
嵌套类机制实现
嵌套类的实现并不是通过修改jvm机制或指令,它的实现是通过编译器特殊编译实现的,相当于是语法糖。每一个嵌套类都会单独生成一个class文件,这个class类名一般是外部类名+$+内部类名,这个生成的类除了有本来的属性跟方法外,还有一个类型为外部类的final属性以及一个参数为外部类的构造方法。内部类调用外部类的变量方法,其实就是调用这个final属性的变量跟方法。而实例化一个内部类就是将那个outerObject作为参数调用内部类的构造方法,实例化一个静态嵌套类就是已null为参数调用构造方法。
遮蔽(Shadowing)
如果在一个特定作用域(比如内部类或一个方法中)声明了一个类型(比如成员变量或参数名),而这个申明跟它的外部作用域有相同的名称,那么内部这个声明遮蔽外部作用域的声明。那就不能直接用这个变量的名称去调用被遮蔽的声明。比如
public class ShadowTest {
public int x = 0;
class FirstLevel {
public int x = 1;
void methodInFirstLevel(int x) {
System.out.println("x = " + x);
System.out.println("this.x = " + this.x);
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
}
}
public static void main(String... args) {
ShadowTest st = new ShadowTest();
ShadowTest.FirstLevel fl = st.new FirstLevel();
fl.methodInFirstLevel(23);
}
}
结果:x = 23
this.x = 1
ShadowTest.this.x = 0
这个例子中定义了三个名称为x的变量:ShadowTest的成员变量、内部类FirstLevel的成员变量和methodInFirstLevel方法的参数。方法参数x遮蔽了FirstLevel的成员变量,因此在方法中使用变量x的时候,它引用的是方法参数。要引用内部类FirstLevel的成员变量,可以使用关键字this来代表外部作用域。要引用更外部做用域的成员变量需要带上该变量所属的类名
序列化
非常不建议序列化包括本地方法、匿名方法在内的内部类。为什么呢,如上面讲的内部类生效是因为java编译器单独为它生产一个class文件,而且他的构造方法是合成的。这个合成的方式虚拟机是不管的,由各家编译器去实现,当然就会有差异性,那么在序列化再反序列化的时候就容易出问题