Java 嵌套类快速入门指南

更新于 2025-12-27

baeldung 2024-06-11

1. 引言

本教程是对 Java 语言中嵌套类(nested classes)的一个简明扼要的介绍。

简单来说,Java 允许我们在一个类内部定义另一个类。嵌套类使我们能够将仅在一处使用的类进行逻辑分组,从而编写出更具可读性和可维护性的代码,并增强封装性。

在开始之前,我们先来看看 Java 中可用的几种嵌套类类型:

  • 静态嵌套类(Static nested classes)
  • 非静态嵌套类(Non-static nested classes)
  • 局部类(Local classes)
  • 匿名类(Anonymous classes)

在接下来的章节中,我们将逐一详细讨论这些类型。


2. 静态嵌套类

关于静态嵌套类,有以下几点需要注意:

  • 与静态成员一样,它们属于其外部类(enclosing class),而不是外部类的某个实例。
  • 它们在声明时可以使用任意访问修饰符(public、private、protected、默认)。
  • 它们只能访问外部类中的静态成员。
  • 它们可以定义静态和非静态成员。

下面是一个静态嵌套类的声明示例:

public class Enclosing {
    
    private static int x = 1;
    
    public static class StaticNested {

        private void run() {
            // 方法实现
        }
    }
    
    @Test
    public void test() {
        Enclosing.StaticNested nested = new Enclosing.StaticNested();
        nested.run();
    }
}

3. 非静态嵌套类

非静态嵌套类也称为内部类(inner classes)。以下是需要记住的几个要点:

  • 它们在声明时也可以使用任意访问修饰符。
  • 与实例变量和方法类似,内部类与外部类的一个实例相关联。
  • 它们可以访问外部类的所有成员(无论是否为静态)。
  • 它们只能定义非静态成员。

内部类的声明方式如下:

public class Outer {
    
    public class Inner {
        // ...
    }
}

如果我们在嵌套类声明中添加 static 修饰符,那么它就是一个静态嵌套类;否则就是内部类。虽然语法上仅相差一个关键字(static),但语义上有很大区别:内部类的实例绑定到外部类的实例,因此可以访问其成员。在决定是否将嵌套类设计为内部类时,应充分意识到这一点。

要实例化一个内部类,必须先实例化其外部类:

Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();

在接下来的小节中,我们将介绍两种特殊的内部类。

3.1. 局部类(Local Classes)

局部类是一种特殊的内部类——它定义在方法或作用域块内部。

关于局部类,需注意以下几点:

  • 它们在声明时不能使用访问修饰符。
  • 它们可以访问外部上下文中的静态和非静态成员。
  • 它们只能定义实例成员。

示例如下:

public class NewEnclosing {
    
    void run() {
        class Local {

            void run() {
                // 方法实现
            }
        }
        Local local = new Local();
        local.run();
    }
    
    @Test
    public void test() {
        NewEnclosing newEnclosing = new NewEnclosing();
        newEnclosing.run();
    }
}

3.2. 匿名类(Anonymous Classes)

匿名类可用于在不创建可重用实现的情况下,实现接口或抽象类。

关于匿名类,需注意以下几点:

  • 它们在声明时不能使用访问修饰符。
  • 它们可以访问外部上下文中的静态和非静态成员。
  • 它们只能定义实例成员。
  • 它们是唯一不能定义构造函数、也不能显式继承类或实现接口(除了通过初始化表达式隐式实现)的嵌套类类型。

首先定义一个简单的抽象类:

abstract class SimpleAbstractClass {
    abstract void run();
}

然后定义一个匿名类:

public class AnonymousInnerUnitTest {
    
    @Test
    public void whenRunAnonymousClass_thenCorrect() {
        SimpleAbstractClass simpleAbstractClass = new SimpleAbstractClass() {
            void run() {
                // 方法实现
            }
        };
        simpleAbstractClass.run();
    }
}

如需了解更多细节,可参考我们关于 Java 匿名类 的专门教程。


4. 名称遮蔽(Shadowing)

如果内部类的成员与外部类的成员同名,则内部类的成员会遮蔽(shadow)外部类的成员。

在这种情况下,this 关键字指向的是嵌套类的实例,而外部类的成员可以通过 外部类名.this 的方式引用。

示例如下:

public class NewOuter {

    int a = 1;
    static int b = 2;

    public class InnerClass {
        int a = 3;
        static final int b = 4;

        public void run() {
            System.out.println("a = " + a);
            System.out.println("b = " + b);
            System.out.println("NewOuter.this.a = " + NewOuter.this.a);
            System.out.println("NewOuter.b = " + NewOuter.b);
            // 注意:不能通过 NewOuter.this.b 访问静态字段 b,
            // 因为 this 指向的是实例,而 b 是静态的
        }
    }

    @Test
    public void test() {
        NewOuter outer = new NewOuter();
        NewOuter.InnerClass inner = outer.new InnerClass();
        inner.run();
    }
}

注意:在内部类中访问外部类的静态成员时,应直接使用 外部类名.静态成员,而不是 外部类名.this.静态成员,因为 this 指向的是实例,而静态成员属于类本身。


5. 序列化(Serialization)

在尝试序列化嵌套类时,为避免出现 java.io.NotSerializableException,应遵循以下原则:

  • 将嵌套类声明为 static(即静态嵌套类);
  • 同时让嵌套类和外部类都实现 Serializable 接口。

6. 结论

在本文中,我们了解了什么是嵌套类,以及它们的不同类型。我们还探讨了在不同类型的嵌套类中,字段可见性和访问修饰符的差异。