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. 结论
在本文中,我们了解了什么是嵌套类,以及它们的不同类型。我们还探讨了在不同类型的嵌套类中,字段可见性和访问修饰符的差异。