1. 内部类概述
在Java中,**内部类(Inner Class)**是定义在另一个类内部的类。根据是否依赖外部类实例,内部类可分为:
- 非静态内部类(成员内部类):持有外部类的隐式引用,可访问其成员。
- 静态内部类(Static Nested Class):不依赖外部类实例,需显式传递引用。
- 局部内部类(Local Class):定义在方法或作用域内。
- 匿名内部类(Anonymous Class):无类名,直接实例化。
本文重点解析 非静态内部类与外部类的交互,以 AbstractLocalCache
为例。
2. 内部类如何访问外部类成员?
2.1 隐式持有外部类引用
非静态内部类默认持有外部类的引用(OuterClass.this
),因此可直接访问外部类的成员(包括私有成员)。
示例代码:
public class Outer {
private String outerField = "Outer";
class Inner {
void print() {
System.out.println(outerField); // 直接访问外部类字段
System.out.println(Outer.this.outerField); // 显式引用(推荐)
}
}
}
2.2 显式引用 OuterClass.this
当内部类与外部类有同名成员时,需通过 OuterClass.this
显式指定访问目标。
示例场景:
public class Outer {
private String name = "Outer";
class Inner {
private String name = "Inner";
void print() {
System.out.println(name); // 输出 "Inner"(内部类成员)
System.out.println(Outer.this.name); // 输出 "Outer"(外部类成员)
}
}
}
3. 案例解析:AbstractLocalCache
的内部类
在 AbstractLocalCache
中,CacheLoader
是一个匿名内部类(实现Caffeine的缓存加载逻辑),需通过 AbstractLocalCache.this
调用外部类的 load()
方法。
3.1 代码解析
cache = Caffeine.newBuilder()
.build(new CacheLoader<IN, OUT>() { // 匿名内部类
@Override
public OUT load(IN in) {
// 通过 AbstractLocalCache.this 调用外部类方法
return AbstractLocalCache.this.load(Collections.singletonList(in)).get(in);
}
});
关键点:
CacheLoader
是匿名内部类,隐式持有AbstractLocalCache
实例的引用。- 直接写
this.load()
会尝试调用CacheLoader.load()
(不存在),导致编译错误。 AbstractLocalCache.this
显式指定调用外部类的load()
方法。
4. 注意事项
4.1 内存泄漏风险
- 问题:非静态内部类隐式持有外部类引用,若内部类实例(如异步任务)生命周期过长,会导致外部类无法被GC回收。
- 解决:
- 若内部类无需访问外部类成员,改为 静态内部类。
- 使用 弱引用(WeakReference) 持有外部类实例。
优化示例:
public class Outer {
private String data = "Sensitive";
static class StaticInner { // 静态内部类不持有外部类引用
void process(Outer outer) {
System.out.println(outer.data); // 需显式传入外部类实例
}
}
}
4.2 序列化问题
- 问题:非静态内部类默认包含外部类引用,序列化时会连带序列化外部类,可能导致异常。
- 解决:
- 改为静态内部类 + 显式传递外部类引用。
- 实现
Serializable
时标记transient
或自定义序列化逻辑。
5. 最佳实践
5.1 优先使用静态内部类
- 适用场景:内部类无需访问外部类实例成员时。
- 优点:减少内存占用,避免隐式引用导致的问题。
示例:
public class CacheUtils {
public static class CacheLoader { // 静态内部类
public void load() {
// 不依赖外部类实例
}
}
}
5.2 显式传递依赖
若内部类需访问外部类状态,通过构造函数或方法参数传入,而非依赖隐式引用。
示例:
public class Outer {
private String config;
public class Inner {
private final Outer outer;
Inner(Outer outer) { // 显式传入外部类引用
this.outer = outer;
}
void print() {
System.out.println(outer.config);
}
}
}
5.3 避免在匿名内部类中修改外部变量
匿名内部类访问的局部变量必须是 final
或等效不可变(Java 8+ 隐式 final
)。
错误示例:
public void process() {
int count = 0;
Runnable task = new Runnable() {
@Override
public void run() {
count++; // 编译错误!count 必须为 final
}
};
}
正确写法:
public void process() {
final AtomicInteger count = new AtomicInteger(0); // 使用原子类
Runnable task = () -> count.incrementAndGet();
}
6. 总结
场景 | 推荐实现 | 原因 |
---|---|---|
内部类需访问外部类成员 | 非静态内部类 + OuterClass.this |
直接访问外部类状态 |
内部类独立于外部类 | 静态内部类 | 避免内存泄漏,减少耦合 |
需要序列化 | 静态内部类 + 显式传递引用 | 避免序列化外部类 |
匿名内部类修改外部变量 | 使用 final 或原子类 |
遵守 Java 变量捕获规则 |
核心原则:
- 明确作用域:始终清楚
this
指向哪个类的实例。 - 最小化依赖:优先选择静态内部类,减少隐式引用。
- 线程安全:避免在内部类中共享可变外部状态。
通过合理使用内部类,可以写出更模块化、可维护的代码,同时避免常见陷阱。