Java内部类二进制名称为“外部类名$内部类名”,如Outer$Inner;匿名类为“外部类名$数字”,如Outer$1,对应生成Outer$Inner.class和Outer$1.class文件,JVM依此加载。

Java内部类的二进制名称和匿名对象在文件系统中的表示,是编译后字节码层面的关键约定,不是源码命名习惯,也不等同于“.class”文件名本身——但二者紧密关联。理解它,能帮你准确定位编译产物、排查类加载异常、读懂反编译结果。
内部类的二进制名称由外层类名 + $ + 内部类名构成
成员内部类(定义在外部类成员位置)的二进制名称严格遵循:外部类二进制名称 + '$' + 简单名称。例如:
- public class Outer { static class StaticInner {} } → 二进制名称为 Outer$StaticInner
- public class Outer { class Inner {} } → 二进制名称为 Outer$Inner(注意:非静态成员内部类隐式持有外部类引用,但命名规则不变)
该名称直接决定编译后生成的 Outer$Inner.class 文件名。JVM 加载时依据的就是这个二进制名称,而非源码中的嵌套写法(如 Outer.Inner)。
匿名类没有简单名称,二进制名含数字序号
匿名类(包括匿名内部类、lambda 表达式生成的类)不声明类名,其二进制名称由包围类的二进制名称 + '$' + 非空数字序列组成。数字从 1 开始,按编译时出现顺序递增:
立即学习“Java免费学习笔记(深入)”;
- 若
Outer中第一次出现new Runnable() { ... },对应类的二进制名是 Outer$1 - 第二次出现,就是 Outer$2;同一方法内多个匿名类也依此编号
因此,匿名对象本身不产生独立变量表示——它只是对某个 Outer$1 类实例的即时使用。你在文件系统中看到的 Outer$1.class,就是该匿名类的字节码载体。
匿名对象不生成.class文件,但依赖其所属匿名类的.class
匿名对象是运行时创建的实例,不单独对应文件系统条目。它的“变量表示”仅存在于栈帧中的一次性引用(如 new Thread(() -> {}).start() 中的临时对象),生命周期随语句结束而不可达。
- 你不会在磁盘上找到名为
AnonymousObject.class的文件 - 但只要代码中存在匿名类,javac 就会生成形如
Outer$1.class的文件,且该文件必须存在,否则运行时报NoClassDefFoundError - 可通过
javap -v Outer\$1.class查看其签名,其中Constant pool会显示其二进制名称(Outer$1)和父类/接口信息
注意区分:规范名称 ≠ 二进制名称 ≠ 文件名(但通常一致)
以 java.util.HashMap.SimpleEntry 为例:
-
规范名称(Canonical Name):是源码可读的点分形式,
java.util.HashMap.SimpleEntry -
二进制名称(Binary Name):JVM 内部使用,用
$替换点,即java.util.HashMap$SimpleEntry -
文件系统名:由二进制名称映射而来,路径分隔符替换为
/,加.class后缀 →java/util/HashMap$SimpleEntry.class
这个映射关系是 Java 类文件格式(JVM Spec §4.2)强制要求的,也是类加载器查找资源的基础逻辑。


















