文档

Java™教程
隐藏目录
嵌套类
路径: 学习Java语言
课程: 类和对象

嵌套类

Java编程语言允许在另一个类中定义一个类。这样的类称为嵌套类,如下所示:

class OuterClass {
    ...
    class NestedClass {
        ...
    }
}

术语: 嵌套类分为两类:非静态和静态。非静态嵌套类称为内部类。声明为static的嵌套类称为静态嵌套类
class OuterClass {
    ...
    class InnerClass {
        ...
    }
    static class StaticNestedClass {
        ...
    }
}

嵌套类是其封装类的成员。非静态嵌套类(内部类)可以访问封装类的其他成员,即使它们被声明为私有。静态嵌套类无法访问封装类的其他成员。作为OuterClass的成员,嵌套类可以声明为privatepublicprotected包私有。(请记住,外部类只能声明为public包私有。)

为什么使用嵌套类?

使用嵌套类的重要原因包括以下几点:

内部类

与实例方法和变量一样,内部类与其封装类的实例相关联,并直接访问该对象的方法和字段。另外,由于内部类与一个实例相关联,它本身不能定义任何静态成员。

作为外部类的一个实例,内部类的实例存在于外部类的实例中。考虑以下类:

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类成员:

OuterClass.java

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);                
    }
}

TopLevelClass.java

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

Shadowing

如果特定作用域(如内部类或方法定义)中的一个类型声明(如成员变量或参数名)与封闭作用域中的另一个声明具有相同的名称,则该声明会屏蔽封闭作用域的声明。您不能仅凭其名称引用被屏蔽的声明。以下示例 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实现中进行反序列化,可能会遇到兼容性问题。有关当编译内部类时生成的合成结构的更多信息,请参阅隐式和合成参数部分以及获取方法参数名称部分。


上一页: 问题和练习: 对象
下一页: 内部类示例