Java教程是针对JDK 8编写的。本页面中描述的示例和实践不利用后续版本中引入的改进,并且可能使用不再可用的技术。
有关Java SE 9及其后续版本中更新的语言特性的摘要,请参阅Java语言更改。
有关所有JDK版本的新功能、增强功能和已删除或已弃用选项的信息,请参阅JDK发行说明。
Java编程语言允许在另一个类中定义一个类。这样的类称为嵌套类,如下所示:
class OuterClass { ... class NestedClass { ... } }
static
的嵌套类称为静态嵌套类。
class OuterClass { ... class InnerClass { ... } static class StaticNestedClass { ... } }
嵌套类是其封装类的成员。非静态嵌套类(内部类)可以访问封装类的其他成员,即使它们被声明为私有。静态嵌套类无法访问封装类的其他成员。作为OuterClass
的成员,嵌套类可以声明为private
、public
、protected
或包私有。(请记住,外部类只能声明为public
或包私有。)
使用嵌套类的重要原因包括以下几点:
它是一种逻辑上将只在一个地方使用的类进行分组的方式:如果一个类只对另一个类有用,那么将其嵌入该类并保持两者在一起是合乎逻辑的。嵌套这样的"辅助类"可以使包更加简洁。
它增加了封装性:考虑两个顶级类A和B,其中B需要访问A的成员,而这些成员通常被声明为private
。通过将类B隐藏在类A内部,A的成员可以声明为私有,并且B可以访问它们。此外,B本身也可以对外界隐藏。
它可以使代码更易读、更易于维护:将小的类嵌套在顶级类中,可以将代码放置在更接近使用它的地方。
与实例方法和变量一样,内部类与其封装类的实例相关联,并直接访问该对象的方法和字段。另外,由于内部类与一个实例相关联,它本身不能定义任何静态成员。
作为外部类的一个实例,内部类的实例存在于外部类的实例中。考虑以下类:
class OuterClass { ... class InnerClass { ... } }
InnerClass
的实例只能存在于OuterClass
的一个实例中,并且可以直接访问其封装实例的方法和字段。
要实例化内部类,必须先实例化外部类。然后,使用以下语法在外部对象中创建内部对象:
OuterClass outerObject = new OuterClass(); OuterClass.InnerClass innerObject = outerObject.new InnerClass();
与类方法和变量一样,静态嵌套类与其外部类相关联。并且,与静态类方法一样,静态嵌套类不能直接引用其封闭类中定义的实例变量或方法:它只能通过对象引用使用它们。内部类和嵌套静态类示例演示了这一点。
您可以像实例化顶级类一样实例化静态嵌套类:
StaticNestedClass staticNestedObject = new StaticNestedClass();
以下示例:OuterClass
,以及TopLevelClass
,演示了内部类(InnerClass
),嵌套静态类(StaticNestedClass
)和顶级类(TopLevelClass
)可以访问的OuterClass
类成员:
public class OuterClass { String outerField = "外部字段"; static String staticOuterField = "静态外部字段"; class InnerClass { void accessMembers() { System.out.println(outerField); System.out.println(staticOuterField); } } static class StaticNestedClass { void accessMembers(OuterClass outer) { // 编译错误:无法对非静态字段 outerField 进行静态引用 // System.out.println(outerField); System.out.println(outer.outerField); System.out.println(staticOuterField); } } public static void main(String[] args) { System.out.println("内部类:"); System.out.println("------------"); OuterClass outerObject = new OuterClass(); OuterClass.InnerClass innerObject = outerObject.new InnerClass(); innerObject.accessMembers(); System.out.println("\n静态嵌套类:"); System.out.println("--------------------"); StaticNestedClass staticNestedObject = new StaticNestedClass(); staticNestedObject.accessMembers(outerObject); System.out.println("\n顶级类:"); System.out.println("--------------------"); TopLevelClass topLevelObject = new TopLevelClass(); topLevelObject.accessMembers(outerObject); } }
public class TopLevelClass { void accessMembers(OuterClass outer) { // 编译错误:无法对非静态字段 OuterClass.outerField 进行静态引用 // System.out.println(OuterClass.outerField); System.out.println(outer.outerField); System.out.println(OuterClass.staticOuterField); } }
该示例将打印以下输出:
内部类: ------------ 外部字段 静态外部字段 静态嵌套类: -------------------- 外部字段 静态外部字段 顶级类: -------------------- 外部字段 静态外部字段
请注意,静态嵌套类与其外部类的实例成员交互的方式与任何其他顶级类一样。静态嵌套类 StaticNestedClass
无法直接访问 outerField
,因为它是封闭类 OuterClass
的实例变量。Java编译器会在标记的语句处生成错误:
static class StaticNestedClass { void accessMembers(OuterClass outer) { // 编译错误:无法对非静态字段 outerField 进行静态引用 System.out.println(outerField); } }
要解决此错误,通过对象引用访问 outerField
:
System.out.println(outer.outerField);
同样,顶级类 TopLevelClass
也无法直接访问 outerField
。
如果特定作用域(如内部类或方法定义)中的一个类型声明(如成员变量或参数名)与封闭作用域中的另一个声明具有相同的名称,则该声明会屏蔽封闭作用域的声明。您不能仅凭其名称引用被屏蔽的声明。以下示例 ShadowTest
演示了这一点:
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
中的参数。方法 methodInFirstLevel
的参数变量 x
屏蔽了内部类 FirstLevel
的变量。因此,当在方法 methodInFirstLevel
中使用变量 x
时,它引用的是方法参数。要引用内部类 FirstLevel
的成员变量,使用关键字 this
表示封闭作用域:
System.out.println("this.x = " + this.x);
通过所属类的类名引用封装更大范围的成员变量。例如,下面的语句从方法methodInFirstLevel
中访问了类ShadowTest
的成员变量:
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
强烈不建议对内部类进行序列化,包括局部类和匿名类。当Java编译器编译某些结构(如内部类)时,会创建合成结构,这些结构包括在源代码中没有对应结构的类、方法、字段和其他构造。合成结构使得Java编译器可以实现新的Java语言特性而无需更改JVM。然而,合成结构在不同的Java编译器实现之间可能会有所不同,这意味着.class
文件也可能因不同实现而有所不同。因此,如果你对内部类进行序列化,然后在不同的JRE实现中进行反序列化,可能会遇到兼容性问题。有关当编译内部类时生成的合成结构的更多信息,请参阅隐式和合成参数部分以及获取方法参数名称部分。