Jakob Jenkov 2015-03-09
在 Java 中,嵌套类(Nested Classes)是指定义在另一个类内部的类。
嵌套类的主要目的是将嵌套类与其外部类清晰地组织在一起,表明这两个类应协同使用;或者表明该嵌套类仅在其封闭(拥有)类内部使用。
Java 开发者通常将嵌套类称为内部类(Inner Classes),但实际上“内部类”只是 Java 中多种嵌套类类型之一。
在 Java 中,嵌套类被视为其外部类的成员。因此,嵌套类可以使用 public、package(无访问修饰符)、protected 和 private 等访问修饰符(详见 访问修饰符)。此外,如 Java 继承 教程所述,嵌套类也可以被子类继承。
Java 支持以下几种不同类型的嵌套类:
- 静态嵌套类(Static Nested Classes)
- 非静态嵌套类(Non-static Nested Classes / Inner Classes)
- 局部类(Local Classes)
- 匿名类(Anonymous Classes)
下面将逐一介绍这些嵌套类类型。
静态嵌套类(Static Nested Classes)
静态嵌套类在 Java 中声明如下:
public class Outer {
public static class Nested {
}
}
要创建 Nested 类的实例,必须通过外部类名进行引用:
Outer.Nested instance = new Outer.Nested();
在 Java 中,静态嵌套类本质上就是一个普通的类,只是被嵌套在另一个类中。由于它是 static 的,因此只能通过外部类的实例引用来访问其非静态成员。
非静态嵌套类(内部类,Inner Classes)
Java 中的非静态嵌套类也被称为内部类(Inner Classes)。内部类与外部类的一个实例相关联,因此必须先创建外部类的实例,才能创建内部类的实例。
内部类的定义示例如下:
public class Outer {
public class Inner {
}
}
创建 Inner 类实例的方式如下:
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
注意:在外部类实例后使用 new 关键字来创建内部类实例。
非静态嵌套类(内部类)可以访问其外部类的所有字段,即使这些字段被声明为 private。例如:
public class Outer {
private String text = "I am private!";
public class Inner {
public void printText() {
System.out.println(text);
}
}
}
注意:Inner 类的 printText() 方法直接引用了 Outer 类的私有字段 text,这是完全合法的。
调用方式如下:
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.printText();
内部类的变量遮蔽(Inner Class Shadowing)
如果 Java 内部类中声明了与外部类同名的字段或方法,则内部类的成员会遮蔽(shadow)外部类的成员。
示例:
public class Outer {
private String text = "I am Outer private!";
public class Inner {
private String text = "I am Inner private";
public void printText() {
System.out.println(text); // 输出 Inner 的 text
}
}
}
在上述例子中,Outer 和 Inner 都包含名为 text 的字段。当 Inner 引用 text 时,它指的是自己的字段。
不过,Java 允许内部类通过 Outer.this.text 的方式显式访问外部类的字段:
public class Outer {
private String text = "I am Outer private!";
public class Inner {
private String text = "I am Inner private";
public void printText() {
System.out.println(text); // Inner 的 text
System.out.println(Outer.this.text); // Outer 的 text
}
}
}
局部类(Local Classes)
局部类是定义在方法内部或方法内的作用域块({ ... })中的类,类似于内部类。
示例:
class Outer {
public void printText() {
class Local {
}
Local local = new Local();
}
}
局部类只能在其定义的方法或作用域块内部访问。
与内部类一样,局部类可以访问其外部类的成员(字段和方法)。
此外,局部类还可以访问所在方法中的局部变量,但这些变量必须被声明为 final。
从 Java 8 开始,局部类还可以访问实际上为 final(effectively final)的局部变量或参数——即初始化后从未被修改的变量。
如果局部类定义在静态方法中,则它只能访问外部类的静态成员。由于局部类本质上是非静态的,因此即使在静态方法中定义,也不能包含大多数静态声明(但允许 static final 常量)。
局部类同样遵循与内部类相同的遮蔽规则。
匿名类(Anonymous Classes)
匿名类是没有类名的嵌套类,通常用于以下两种场景:
- 作为现有类的子类(继承并重写方法)
- 实现某个接口
匿名类在实例化时同时定义。例如,以下代码定义了一个 SuperClass 的匿名子类:
public class SuperClass {
public void doIt() {
System.out.println("SuperClass doIt()");
}
}
SuperClass instance = new SuperClass() {
public void doIt() {
System.out.println("Anonymous class doIt()");
}
};
instance.doIt(); // 输出: Anonymous class doIt()
匿名类也可以实现接口:
public interface MyInterface {
public void doIt();
}
MyInterface instance = new MyInterface() {
public void doIt() {
System.out.println("Anonymous class doIt()");
}
};
instance.doIt();
匿名类可以访问其外部类的成员,也可以访问所在方法中 final 或 effectively final 的局部变量(Java 8 起)。
你可以在匿名类中声明字段和方法,但不能声明构造函数。不过,你可以使用静态初始化块(static initializer)来初始化字段:
final String textToPrint = "Text...";
MyInterface instance = new MyInterface() {
private String text;
// 静态初始化块(实际是非静态初始化块,因匿名类非静态)
{
this.text = textToPrint;
}
public void doIt() {
System.out.println(this.text);
}
};
instance.doIt();
匿名类同样适用与内部类相同的遮蔽规则。
嵌套类的优势(Nested Class Benefits)
使用 Java 嵌套类的主要优势在于:将逻辑上紧密相关的类组合在一起。
虽然你也可以通过将类放在同一个包中实现分组,但将一个类嵌套在另一个类内部表达了更强的关联性。
嵌套类通常只被其外部类使用或与之配合使用。有时,嵌套类对外部完全不可见(如 private 嵌套类),仅用于内部实现;有时则对外暴露,但必须与外部类一起使用。
示例:缓存类(Cache)
假设你有一个 Cache 类,内部需要一个 CacheEntry 类来存储缓存项的元数据(如缓存值、插入时间、访问次数等)。
用户可能只需要获取缓存值,而无需知道 CacheEntry 的存在;但有时你也希望暴露 CacheEntry,以便用户获取更多元信息(如最后刷新时间)。
示例 1:隐藏嵌套类
public class Cache {
private Map<String, CacheEntry> cacheMap = new HashMap<>();
private class CacheEntry {
public long timeInserted = 0;
public Object value = null;
}
public void store(String key, Object value) {
CacheEntry entry = new CacheEntry();
entry.value = value;
entry.timeInserted = System.currentTimeMillis();
this.cacheMap.put(key, entry);
}
public Object get(String key) {
CacheEntry entry = this.cacheMap.get(key);
if (entry == null) return null;
return entry.value;
}
}
在此版本中,CacheEntry 是 private 的,对外不可见。
示例 2:暴露嵌套类
public class Cache {
private Map<String, CacheEntry> cacheMap = new HashMap<>();
public class CacheEntry {
public long timeInserted = 0;
public Object value = null;
}
public void store(String key, Object value) {
CacheEntry entry = new CacheEntry();
entry.value = value;
entry.timeInserted = System.currentTimeMillis();
this.cacheMap.put(key, entry);
}
public Object get(String key) {
CacheEntry entry = this.cacheMap.get(key);
if (entry == null) return null;
return entry.value;
}
public CacheEntry getCacheEntry(String key) {
return this.cacheMap.get(key);
}
}
此版本将 CacheEntry 设为 public,允许外部代码直接访问缓存条目对象。