Java 内部类要点总览
内部类类别 | 典型定义位置 | .class 文件名模式 | 是否依赖外部实例 (this$0 ) | 是否可捕获局部变量 (val$… ) | 访问限定 |
---|---|---|---|---|---|
成员内部类(非 static) | 外部类体中 | Outer$Inner.class | 是 | 不涉及(不能定义在方法里) | 与外部类成员同级,可用修饰符控制 |
静态嵌套类 | static class Nest {} | Outer$Nest.class | 否 | — | 类似顶层类,被外部包围于命名空间 |
局部内部类 | 方法 / 代码块中命名定义 | Outer$1Local.class | 取决于上下文: • 实例方法 → 有 • 静态方法 → 无 | 只对引用的 final/effectively‑final 变量生成 | 仅在声明块内部可见 |
匿名内部类 | new Super() { … } | Outer$2.class (数字递增) | 同上 | 同上 | 仅通过引用持有 |
1. 命名解析 & 字段隐藏顺序
-
局部变量 / 形参
-
当前类自己声明的字段
-
从父类 / 接口继承的字段(最近父类优先)
-
封闭类(
Outer
→Outer
的父类 …)层层向外 -
仍找不到 → 编译错误
一旦命中,外层同名字段被隐藏,必须用限定名
Outer.name
访问。
2. this
、this$0
与生命周期
场景 | 隐式 this | this$0 字段 | 担当职责 |
---|---|---|---|
实例成员方法 | 有 (this 本类) | — | 访问本类字段 |
静态成员方法 | 无 | — | — |
非 static 内部类 / 局部类 / 匿名类(实例上下文) | 有 | 有 | 为内部类在方法返回后继续持有外部对象引用 |
相同但出现在静态上下文 | 有 | 无 | 无外部实例可捕获 |
3. 捕获局部变量
步骤 | 说明 |
---|---|
① 判断变量是否被内部/匿名类引用 | 若未引用 → 不做任何事 |
② 校验变量是否 final 或 effectively‑final | 只赋值一次即可视为 effectively‑final(Java 8+) |
③ 为每个被引用变量生成 val$… 字段 | final T val$var; |
④ 在构造器首位插入同类型形参 | 初始化快照:this.val$var = var; |
⚠ 若变量后来再次赋值 | 直接编译错误:必须保持 effectively‑final |
可变需求 → 用可变对象(AtomicInteger
、数组、StringBuilder
…)或提升为外部类字段。
4. 本地类 & 匿名类的加载时机
-
编译阶段始终产生独立的
.class
文件。 -
JVM 仅在首次执行到
new
该类的位置时才加载,因此调试器/类浏览器里看不到 = 还未加载。
5. 变量作用域与安全性动机
-
方法栈帧消失 → 捕获值必须搬到堆上(内部类字段)。
-
禁止二次赋值可避免“快照值”与原局部变量脱离同步。
-
this$0
与val$…
统一采用 final 字段 + 构造器注入 实现,简单、高效、易被 JIT 优化。
6. 其他易被忽略的细节
细节 | 说明 |
---|---|
枚举、注解 实际都是静态嵌套类 | 编译器自动加 static 。 |
内部类访问权限修饰符 | 成员内部类支持 public / protected / private / package ;局部、匿名类没有显式修饰符,只在块内可见。 |
反射取到的字段名 this$0 / val$1 | 标记为 synthetic ,不计入 getDeclaredFields() 默认返回(可见性受 setAccessible 控制)。 |
Outer.this 语法 | 仅在非静态内部/局部/匿名类中合法;静态上下文写它直接编译错误。 |
7. 速查结论
问题 | 答案一行说明 |
---|---|
局部/匿名类必须捕获外部实例吗? | 仅当定义在实例上下文才捕获;静态上下文无 this$0 。 |
捕获局部变量一定要 final 吗? | Java 8+ 只需 “赋值一次” 即视为 effectively‑final。 |
能在内部类里给局部变量重新赋值吗? | 不行;重赋值破坏 effectively‑final,编译失败。 |
静态方法里的局部内部类能访问外部类字段吗? | 只能访问外部类的 static 字段;无外部实例引用。 |
参考思维导图
内部类
├─ 成员内部类 (非static)
│ ├─ 拥有 this$0
│ └─ 与外部字段/方法互访
├─ 静态嵌套类
│ └─ 无 this$0,似顶层类
├─ 局部类
│ ├─ 实例环境 → 有 this$0
│ └─ 静态环境 → 无 this$0
└─ 匿名类
├─ 文件名 Outer$N.class
├─ 规则同局部类
└─ 依赖实例上下文与否决定 this$0
总结
Java 为了让内部类安全且高效地访问外部作用域,采用“构造期捕获 + final 快照”策略:
this$0
捕获外部实例(仅在实例上下文)
val$…
捕获局部变量(必须 final/effectively‑final)
通过禁止后续重新赋值,避免生命周期错位导致的数据不一致,同时保持 JVM 对象模型简单,利于 JIT 优化。
补充:
匿名内部类的构造方法数量永远只有 1 个——编译器自动合成,源码里不能再写其它构造器。
public class Test2 {public static void main(String[] args) {// 匿名内部类1 (静态方法里面)Animal dog = new Animal(){@Overridepublic void eat() {System.out.println("啃骨头");}};dog.eat();}// 匿名内部类2 (成员变量位置)Animal cat = new Animal("小花"){@Overridepublic void eat() {System.out.println("吃老鼠");}};public void show(){// 匿名内部类3 (成员方法里面)int num = 10;Animal tiger = new Animal("小虎",10){@Overridepublic void eat() {System.out.println("吃牛排");}public void getNum(){System.out.println(num);}};}
}
javap反编译后的匿名内部类结构图: