内部类与访问外部类
使用持有外部类实例引用的非静态内部类
内部类与访问外部类 是 CoddyKit 上的免费 Java Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。
内部类
非静态内部类会隐式持有对外部类实例的引用。它可以访问外部类的所有成员,包括私有成员。
声明和使用内部类
只能通过外部类实例创建内部类实例,使用方式为 outer.new InnerClass()。
class BankAccount {
private double balance;
private String owner;
BankAccount(String owner, double initial) {
this.owner = owner; this.balance = initial;
}
class Transaction {
void deposit(double amount) {
balance += amount; // accesses outer field directly
System.out.println(owner + " deposited " + amount);
}
}
}
BankAccount acct = new BankAccount("Alice", 500);
BankAccount.Transaction tx = acct.new Transaction();
tx.deposit(100); // Alice deposited 100.0访问外部成员
内部类可以访问外部类的所有成员,包括私有字段和方法。
class Encryption {
private final String secretKey = "s3cr3t"; // private!
class Encryptor {
String encrypt(String data) {
// Inner class accesses private outer field
return data + "|" + secretKey.hashCode();
}
}
class Decryptor {
String decrypt(String encrypted) {
int idx = encrypted.lastIndexOf('|');
return encrypted.substring(0, idx);
}
}
}使用 this 处理名称遮蔽
当内部类拥有与外部类同名的字段时,请使用 OuterClass.this.field 引用外部类中的字段。
class Outer {
int value = 10;
class Inner {
int value = 20; // shadows outer.value
void printBoth() {
System.out.println("Inner value: " + value); // 20
System.out.println("Outer value: " + Outer.this.value); // 10
}
}
}
new Outer().new Inner().printBoth();
// Inner value: 20
// Outer value: 10使用内部类实现迭代器模式
一个经典用例是:将迭代器实现为内部类,使其能够访问外部集合的私有数据。
class NumberRange implements Iterable<Integer> {
private final int start;
private final int end;
NumberRange(int start, int end) {
this.start = start; this.end = end;
}
@Override
public java.util.Iterator<Integer> iterator() {
return new RangeIterator(); // inner class instance
}
private class RangeIterator implements java.util.Iterator<Integer> {
private int current = start; // accesses outer start
public boolean hasNext() { return current <= end; }
public Integer next() { return current++; }
}
}
for (int n : new NumberRange(1, 5)) System.out.print(n + " ");
// 1 2 3 4 5作为内部类的事件监听器
GUI 和事件驱动编程通常使用内部类实现事件监听器,从而访问外部用户界面组件的状态。
// Swing-style example (conceptual)
class LoginPanel {
private String username = "";
class LoginButtonListener {
void onButtonClicked() {
if (username.isEmpty()) {
System.out.println("Please enter username"); // accesses outer field
} else {
System.out.println("Logging in as: " + username);
}
}
}
void setUsername(String name) { this.username = name; }
}
LoginPanel panel = new LoginPanel();
panel.setUsername("alice");
panel.new LoginButtonListener().onButtonClicked();
// Logging in as: alice集合中的内部类
某些集合实现会使用内部类保存游标或视图,这些游标或视图由外部集合提供支持。
class SimpleStack<T> {
private Object[] elements;
private int size = 0;
SimpleStack(int capacity) {
elements = new Object[capacity];
}
void push(T item) {
if (size >= elements.length) throw new StackOverflowError();
elements[size++] = item;
}
T pop() {
if (size == 0) throw new java.util.EmptyStackException();
T item = (T) elements[--size];
elements[size] = null;
return item;
}
int size() { return size; }
}内存泄漏警告
内部类实例会持有对外部实例的引用。如果内部类脱离外部类实例的作用域(例如被存储在长期存在的集合中),外部实例就无法被垃圾回收。
// Memory leak: anonymous inner class (implicit outer ref) stored statically
class LeakExample {
byte[] data = new byte[1024 * 1024]; // 1MB
// BAD: this Runnable holds a reference to LeakExample.this
Runnable leak = new Runnable() {
public void run() { System.out.println(data.length); }
};
}
// If 'leak' is stored somewhere, LeakExample (and its 1MB) is never GC'd
// FIX: use a static nested class or a lambda that captures only needed values
static Runnable noLeak(byte[] data) {
return () -> System.out.println(data.length); // captures array, not outer
}内部类与 Lambda 的比较
现代 Java 中的 Lambda 表达式通常会替代单方法接口的匿名内部类。Lambda 表达式更加简洁,但 NOT 拥有自己的 this。
// Old way: anonymous inner class
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Running (inner class) — this is the Runnable");
}
};
// Modern: lambda
Runnable r2 = () -> System.out.println("Running (lambda)");
r1.run();
r2.run();
// Lambda this = enclosing class (not the Runnable)
// Inner class this = the Runnable instance itself何时使用内部类
在以下情况下,请使用非静态内部类:
- 该类需要访问外部实例的私有状态
- 每个内部实例在逻辑上都与特定的外部实例关联
- 使用迭代器模式封装遍历逻辑
实践:Paginator
一个实用的 Paginator,其中内部类保存页面状态并访问外部类的数据列表。
import java.util.*;
class Paginator<T> {
private final List<T> items;
private final int pageSize;
Paginator(List<T> items, int pageSize) {
this.items = items; this.pageSize = pageSize;
}
class Page {
private int pageIndex = 0;
List<T> current() {
int start = pageIndex * pageSize;
int end = Math.min(start + pageSize, items.size()); // outer
return start < items.size() ? items.subList(start, end) : List.of();
}
boolean hasNext() { return (pageIndex + 1) * pageSize < items.size(); }
void next() { if (hasNext()) pageIndex++; }
}
}
Paginator<String> p = new Paginator<>(List.of("a","b","c","d","e"), 2);
Paginator.Page page = p.new Page();
System.out.println(page.current()); // [a, b]
page.next();
System.out.println(page.current()); // [c, d]快速检查
内部类(非静态)持有什么,而静态嵌套类不持有?
回顾:内部类与外部类访问
要点:
- 非静态内部类会隐式持有对外围外部实例的引用
- 只能通过外部实例创建内部类实例:outer.new Inner()
- 内部类可以访问外部类的所有成员,包括私有字段
- 使用 Outer.this.field 消除字段遮蔽造成的歧义
- 最适合的场景:Iterator、事件监听器和内部数据结构
- 风险:内部类实例会阻止外部实例被垃圾回收
用 AI 导师学习 Java — 免费
在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。
- 课程
- 104
- 课程
- 374
常见问题解答
「内部类与访问外部类」课时是免费的吗?
是的 — 「内部类与访问外部类」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。
「内部类与访问外部类」这节课中我会学到什么?
使用持有外部类实例引用的非静态内部类 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Java Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「内部类与访问外部类」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Java Academy 课中编写并运行代码吗?
能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。