嵌套类nested classes
定义在类内部的类,叫做嵌套类。
class OuterClass {
...
class NestedClass {
...
}
}
根据类是否为静态的,嵌套类可以分为静态嵌套类和内部类。
(嵌套类、静态嵌套类、内部类)。
嵌套类是外部类的成员,内部类(非静态嵌套类)可以访问外部类的成员,即使是那些私有成员。静态嵌套类不能访问外部类的成员。嵌套类可以是private public protected甚至是package private(modifiers)。外部类只能是public。
使用嵌套类原因
- 只在一个地方使用的一种逻辑地组织方法。如果一个类只对另一个类有用,把它嵌套到另一个类中是符合逻辑的,并且这样的“帮助类”使得整个package结构更合理。
- 增加封装性。类A和B,如果B需要访问A的成员,但是A的成员需要对外不可见(即private类型),那么B可以作为A的内部类,进而访问那些成员,而且,B也可以对外隐藏起来。
- 可读性和便于维护。嵌套类和它们的外部类位置更近。
静态嵌套类
就像类的方法和变量,静态嵌套类和它的外部类是关联的。并且像静态方法一样,静态嵌套类不能直接访问外部类的实例变量或方法,只能通过对象引用。静态嵌套类与它的外部类(和其他类)的实例成员交互,就像其他顶级类一样;效果上,就像是顶级类在另一个顶级类中一样:
访问静态嵌套类:
OuterClass.StaticNestedClass
创建静态嵌套类的对象,语法:
OuterClass.StaticNestedClass nestedClass = new OuterClass.StaticNestedClass();
内部类
像实例方法和变量,内部类与其外部类的实例相关联,并且可以直接访问那个对象的方法和域。因为内部类和实例关联,它不能定义静态成员。
class OuterClass {
...
class InnerClass {
...
}
}
内部类的实例成员,存在于外部类的实例中。一个InnerClass的实例,只能在OuterClass的实例中存在并且可以直接访问外部实例的成员。
初始化一个内部类,必须定义和初始化外部类。在外部类对象内创建内部类对象:
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
有两种特殊类型内部类:局部类(local classes)和匿名类(anonymous classes)。
局部类 (local classes)
在方法中声明的内部类。
匿名类 (anonymous classes)
方法中声明的没有名字的内部类。就像没有名字的局部类。在只需要只用局部类一次的时候,使用匿名类。
声明匿名类
局部类是类的声明,而内部类是表达式。局部类和内部类:
public class HelloWorldAnonymousClasses {
interface HelloWorld {
public void greet();
}
public void sayHello() {
class EnglishGreeting implements HelloWorld {
public void greet() {
}
}
HelloWorld englishGreeting = new EnglishGreeting(); // local class
HelloWolrd frenchGreeting = new HelloWorld() { // anonymous class
String name = "tout le monde";
public void greet() {
System.out.println(name);
}
};
englishGreeting.greet();
frenchGreeting.greet();
}
public static void main(String ... args) {
HelloWorldAnonymousClasses myApp = new HelloWorldAnonymousClasses();
myApp.sayHello();
}
}
匿名类语法
匿名类表达式就像调用一个constructor,只不过代码快中含有对类的定义。
匿名类表达式包含:
- new
- 要实现的接口或要继承的类
- 一对括号,含有constructor的参数,就像初始化类的对象。因为接口没有constructor,所以接口的括号内总是空的。
- 类的声明体。其中,可以有函数声明但不能有语句。
因为匿名类的定义是表达式,因此它必须是表达式的一部分。上面例子中,初始化frenchGreeting对象的表达式中有匿名类定义的表达式。
外部作用域的局部变量,声明和访问匿名类的成员
- 匿名类可以访问其enclosing class的成员。
- 匿名类不能访问作用域内的非final或者等效final类型的局部变量。
- 与嵌套类有相同的shadowing
限制: - 匿名类中不能有静态初始化或者成员接口
- 匿名类不能有constructor
- 可以有静态成员即常量
- 可以有域、额外的方法(接口或父类之外的方法)、实例初始化、局部类
修饰符(Modifiers)
就像其他类成员一样,public private protected 和package private
Shadowing
同名声明遮蔽问题,无法仅仅用名字访问一个被遮蔽的声明。
public class ShadowTest {
public int x = 0;
class FirstLevel { // package private inner class.
public int x = 1;
void methodInFirstLevel(int x) {
System.out.println("x = " + x); // 23
System.out.println("this.x = " + this.x); // 1
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x); // 0
}
}
public static void main(String ... args) {
ShadowTest st = new ShadowTest();
ShadowTest.FirstLevel fl = st.new FirstLevel();
fl.methodInFirstLevel(23);
}
}
定义了三个变量x,ShadowTest
成员变量,内部类FirstLevel
成员变量,函数methodInFirstLevel
的参数。methodInFirstLevel
函数的参数x遮蔽了FirstLevel
的成员变量,因此用this
指明作用域(enclosing scope)。
通过类名,引用更大范围作用域的成员变量。ShadowTest.this.x
,从methodInFirstLevel
函数访问ShadowTest
的成员变量x
序列化
非常不鼓励序列化内部类,包括匿名内部类和局部类。当编译器编译某些构建时,例如内部类,它创建一个synthetic constructs:类、方法、域和其他构建,在源码中没有相应构建。它使得编译器实现新的java语言特性,而不用改变jvm。然而不同编译器实现起来可能不同,以为着.class文件可能不同。因此如果序列化和反序列化的JRE实现不同,会有兼容问题。