1. 什么是内部类(inner class)?
在 Java 中,类不仅可以在顶层(文件中)声明,也可以在另一个类的内部声明。这样的类称为嵌套类(nested class)。如果这样的类在没有 static 修饰符的情况下声明,它就被称为内部类(即 non-static inner class,或简称 inner class)。
内部类是定义在另一个类内部并与该外部类实例关联的类。它可以访问外部类的所有字段和方法,即使它们是 private。这就好比对象有一位“秘密助手”,它几乎什么都能做!
这大致就像房子与房间。房子是外部类,房间是内部类。房间不能脱离房子而存在,但可以使用它的资源:照明、供暖、家具。如果房子消失,房间也会消失。Java 中的内部类也是如此!
为什么需要内部类?
- 逻辑关联:当一个类只用于配合另一个类且脱离它就没有意义时。
- 封装:可以隐藏实现细节,不会污染包级命名空间。
- 访问私有成员:内部类可以访问外部类的私有字段和方法。
- 简洁:减少包级“噪声”类的数量。
内部类的声明语法
声明内部类很简单:在另一个类的类体中声明,且不带 static 修饰符。
class Outer {
// 外部类的字段和方法
class Inner {
// 内部类的字段和方法
void printHello() {
System.out.println("Hello from Inner!");
}
}
}
这里 Inner 相对于 Outer 就是内部类。
示意图:可视化
Outer
│
├─ 字段/方法
│
└─ Inner(内部类)
└─ 自己的字段/方法
2. 如何创建内部类实例
这里藏着新手最常踩的坑之一!内部类的实例总是与某个外部类对象绑定。
示例:
Outer outer = new Outer(); // 创建外部类对象
Outer.Inner inner = outer.new Inner(); // 通过外部对象创建内部类!
inner.printHello(); // Hello from Inner!
如果仅仅写 new Inner() 会导致编译错误,因为 Java 不知道应该把该内部类绑定到哪个外部类对象上。
为什么会这样?
内部类可以访问外部类的字段和方法,因此它必须“知道”自己正在与哪个外部类对象配合工作。
3. 内部类的使用示例
我们来看一个内部类真正有用的例子。
示例 1:“背包与物品”的模型
假设我们有一个 Backpack 类,它可以存放物品(Item)。但我们希望 Item 只在 Backpack 内可用,因为离开背包我们并不关心这些物品。
public class Backpack {
private String owner;
public Backpack(String owner) {
this.owner = owner;
}
// 内部类——物品只能存在于背包中!
class Item {
private String name;
public Item(String name) {
this.name = name;
}
public void printInfo() {
// 魔法!能看到外部类的私有字段
System.out.println(owner + " 拥有物品: " + name);
}
}
}
用法:
Backpack bp = new Backpack("小明");
Backpack.Item item = bp.new Item("Java 教材");
item.printInfo(); // 小明 拥有物品: Java 教材
注意:Item 即使字段 owner 是 private,也可以访问它!
示例 2:自定义集合的迭代器
在 Java 中,集合常常通过内部类实现迭代器。我们来实现一个带内部迭代器的简单集合。
public class IntList {
private int[] data = new int[10];
private int size = 0;
public void add(int value) {
data[size++] = value;
}
// 内部类——迭代器!
class Iterator {
private int index = 0;
public boolean hasNext() {
return index < size;
}
public int next() {
return data[index++];
}
}
}
用法:
IntList list = new IntList();
list.add(10);
list.add(20);
IntList.Iterator it = list.new Iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
4. 内部类的特点
访问外部类成员
内部类可以访问任何(即使是 private)外部类的字段和方法。这很方便,但也需要谨慎:如果外部类发生变化,内部类可能会突然“崩”。
class Outer {
private int secret = 42;
class Inner {
void showSecret() {
System.out.println("秘密:" + secret);
}
}
}
内部类不能包含静态成员
内部(non-static inner)类不能包含静态字段或方法,除了 static final 常量(例如,public static final int MY_CONST = 123;)。这是因为内部类总是“绑定”到外部类的实例。
如果需要声明静态嵌套类——请使用 static 修饰符(这将在下一讲中介绍)。
内部类的可见性
内部类可以是 private、protected、public,或包级可见(package-private)。
public class Outer {
private class Inner { /* ... */ }
}
5. 有用的细节
何时使用内部类
内部类不是为了“好看”,而是为了代码的逻辑组织。请在以下场景使用:
- 类只在一个地方需要(例如辅助迭代器、处理器、复杂结构的一部分)。
- 类与外部类强相关,脱离它就没有意义。
- 希望对包内其他类隐藏实现细节。
不要为了“赶时髦”而使用内部类! 如果该类在其他地方也可能有用,最好将其提升为顶层类。
内部类与 this
在内部类中,可以通过 OuterClassName.this 访问外部类的字段和方法。
class Car {
private String model = "Tesla";
class Engine {
void printModel() {
// 显式访问外部对象
System.out.println("型号:" + Car.this.model);
}
}
}
当内外部类有同名字段时,这种语法尤其有用。
何时不应使用内部类
在以下情况下不要使用:
- 内部类不访问外部类的字段/方法
- 该类在程序的其他地方也可能有用
- 外部类已经变得过大且复杂
这时更好的选择是:
- 将其改为静态嵌套类(static class)
- 将类抽到单独文件
- 用普通方法替代整个类
6. 使用内部类时的常见错误
错误 № 1:试图在没有外部类对象的情况下创建内部类。
如果直接写 new Inner(),编译器会报错:"No enclosing instance of type Outer is accessible"。务必通过外部类对象来创建内部类:outer.new Inner()。
错误 № 2:试图在内部类中声明静态字段或方法。
内部类不能包含静态成员(常量除外)。如果需要——请使用静态嵌套类,也就是用 static 修饰符来声明该类。
错误 № 3:过度使用内部类。
如果内部类没有使用外部类的字段/方法,它很可能应该是 static,或者干脆移到外部。不要“以防万一”就创建内部类。
错误 № 4:混淆对外部类字段的访问。
如果内外部类有同名字段,请使用 OuterClassName.this.field 来显式访问外部类的字段。
GO TO FULL VERSION