乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

成员内部类

白歌(架构师):"小崔,你在Employee类里直接定义的Address类,这就是成员内部类。它的核心特点是持有外部类实例的引用,所以能访问外部类的所有成员,包括 private 的。不过这也意味着必须先有外部类对象才能创建内部类对象。"

小崔(后端开发):"明白了,所以那个 Outer$Inner.class 的文件名里的 $ 符号就是编译器用来连接外部类和内部类名称的?"


定义与语法

成员内部类(Member Inner Class) 是定义在另一个类的内部、方法外部的类。它是外部类的一个成员,就像字段和方法一样。

语法表格

要素说明
定义位置外部类的类体中,所有方法之外
访问修饰符可以加 public、protected、private、默认包访问
修饰符限制不能使用 static(那就是静态内部类了)
创建方式Outer.Inner inner = outer.new Inner()
持有引用每个成员内部类对象都隐式持有指向外部类对象的引用 Outer.this
.class 文件名Outer$Inner.class
访问外部成员可访问外部类的所有成员(包括 private)

基础语法示例

public class Outer {
    private String name = "Outer";

    // 成员内部类
    public class Inner {
        public void print() {
            // 直接访问外部类的 private 成员
            System.out.println("外部类的 name = " + name);
            // 等价于: System.out.println("外部类的 name = " + Outer.this.name);
        }
    }
}

创建内部类对象

public class Outer {
    private String name;

    public Outer(String name) {
        this.name = name;
    }

    public class Inner {
        public void display() {
            System.out.println("外部类实例: " + Outer.this.name);
            System.out.println("内部类实例: " + this);  // Inner 的 this
        }
    }

    public static void main(String[] args) {
        // ⚠️ 关键:必须先有外部类对象
        Outer outer = new Outer("飞翔科技");
        Inner inner = outer.new Inner();  // outer.new Inner()
        inner.display();

        // 或者一步到位
        Inner inner2 = new Outer("一步创建").new Inner();
        inner2.display();
    }
}
运行输出:
外部类实例: 飞翔科技
内部类实例: Outer$Inner@15db9742
外部类实例: 一步创建
内部类实例: Outer$Inner@6d06d69c

深度原理分析:字节码层面的实现

1. 编译器如何处理成员内部类

编写以下类:

public class Outer {
    private int x = 10;

    public class Inner {
        public void printX() {
            System.out.println(x);
        }
    }
}

编译后生成两个 .class 文件:

  • Outer.class
  • Outer$Inner.class

使用 javap -p Outer$Inner 查看内部类的结构:

public class Outer$Inner {
    final Outer this$0;    // 编译器自动添加的字段,指向外部类对象
    public Outer$Inner(Outer);  // 构造方法接收外部类引用
    public void printX();
}

使用 javap -c Outer$Inner 查看 printX() 方法的字节码:

public void printX();
    Code:
       0: getstatic     #2   // Field java/lang/System.out
       3: aload_0
       4: getfield      #1   // Field this$0:LOuter;
       7: invokestatic  #3   // Method Outer.access$000:(LOuter;)I
      10: invokevirtual #4   // Method java/io/PrintStream.println
      13: return

2. 编译器合成的桥接方法

关键发现:invokestatic #3 // Method Outer.access$000:(LOuter;)I

编译器自动为 Outer 生成了一个 access$000 静态方法,用于让内部类访问外部类的私有成员:

// 编译器自动生成的桥接方法(源码中不可见)
static int access$000(Outer o) {
    return o.x;
}

这就是成员内部类能访问外部类 private 成员的底层机制——编译器自动生成包级可见的静态桥接方法。

3. 内存模型


完整示例:飞翔科技员工与地址管理

场景

飞翔科技人事系统需要管理员工信息,每个员工有通讯地址和紧急联系人地址两个地址,这两个 Address 实体只在 Employee 上下文中使用,不需要暴露给其他模块。

示例一:员工通讯录

public class Employee {
    private String id;
    private String name;
    private Address officeAddress;   // 办公地址
    private Address homeAddress;      // 家庭地址

    // 构造方法
    public Employee(String id, String name) {
        this.id = id;
        this.name = name;
    }

    // ========== 成员内部类:Address ==========
    public class Address {
        private String province;
        private String city;
        private String detail;
        private String type;  // "办公" 或 "家庭"

        public Address(String province, String city, String detail, String type) {
            this.province = province;
            this.city = city;
            this.detail = detail;
            this.type = type;
        }

        public void printAddress() {
            // 访问外部类的私有成员 id 和 name
            System.out.println("员工 [" + Employee.this.id + "] " + Employee.this.name);
            System.out.println("  └─ " + type + "地址: "
                + province + "省 " + city + "市 " + detail);
        }
    }

    // 设置地址
    public void setOfficeAddress(String province, String city, String detail) {
        this.officeAddress = new Address(province, city, detail, "办公");
    }

    public void setHomeAddress(String province, String city, String detail) {
        this.homeAddress = new Address(province, city, detail, "家庭");
    }

    public void printAllAddresses() {
        if (officeAddress != null) officeAddress.printAddress();
        if (homeAddress != null) homeAddress.printAddress();
    }

    public static void main(String[] args) {
        // 创建员工
        Employee xiaoCui = new Employee("EMP001", "小崔");
        Employee kongLan = new Employee("EMP002", "孔蓝");

        // ⚠️ 必须通过外部类实例创建内部类对象
        // 正确写法:
        Employee.Address addr1 = xiaoCui.new Address("广东", "深圳", "南山科技园A栋", "临时");

        // 错误写法(编译不通过):
        // Address addr2 = new Address("北京", "北京", "朝阳区", "办公");
        // 错误信息: No enclosing instance of type Employee is accessible

        xiaoCui.setOfficeAddress("广东", "深圳", "南山科技园A栋");
        xiaoCui.setHomeAddress("湖北", "武汉", "洪山区珞喻路1037号");

        kongLan.setOfficeAddress("广东", "深圳", "南山科技园A栋");
        kongLan.setHomeAddress("湖南", "长沙", "岳麓区麓山南路932号");

        System.out.println("========== 小崔的地址 ==========");
        xiaoCui.printAllAddresses();
        System.out.println();
        System.out.println("========== 孔蓝的地址 ==========");
        kongLan.printAllAddresses();

        // 验证 .class 文件名
        System.out.println();
        System.out.println("内部类类名: " + Address.class.getName());
        // 输出: Employee$Address
    }
}
运行输出:
========== 小崔的地址 ==========
员工 [EMP001] 小崔
  └─ 办公地址: 广东省 深圳市 南山科技园A栋
员工 [EMP001] 小崔
  └─ 家庭地址: 湖北省 武汉市 洪山区珞喻路1037号

========== 孔蓝的地址 ==========
员工 [EMP002] 孔蓝
  └─ 办公地址: 广东省 深圳市 南山科技园A栋
员工 [EMP002] 孔蓝
  └─ 家庭地址: 湖南省 长沙市 岳麓区麓山南路932号

内部类类名: Employee$Address

示例二:迭代器模式实现

成员内部类最常见的经典应用是实现迭代器。这正是 ArrayList 的 Itr 和 ListItr 内部类的设计模式:

import java.util.Arrays;

public class SimpleList<T> {
    private Object[] elements;
    private int size;

    public SimpleList() {
        elements = new Object[16];
        size = 0;
    }

    public void add(T element) {
        if (size == elements.length) {
            elements = Arrays.copyOf(elements, size * 2);
        }
        elements[size++] = element;
    }

    public int size() {
        return size;
    }

    @SuppressWarnings("unchecked")
    public T get(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("索引: " + index + ", 大小: " + size);
        }
        return (T) elements[index];
    }

    // ========== 成员内部类:迭代器 ==========
    // 它能访问外部类的 elements 和 size 私有成员
    public class Iterator {
        private int cursor = 0;  // 当前遍历位置

        public boolean hasNext() {
            return cursor < size;  // 直接访问外部类的 size
        }

        @SuppressWarnings("unchecked")
        public T next() {
            if (!hasNext()) {
                throw new IndexOutOfBoundsException("没有更多元素");
            }
            return (T) elements[cursor++];  // 直接访问外部类的 elements
        }
    }

    public Iterator iterator() {
        return new Iterator();  // 等价于 this.new Iterator()
    }

    public static void main(String[] args) {
        SimpleList<String> departments = new SimpleList<>();
        departments.add("技术部");
        departments.add("市场部");
        departments.add("人力资源部");
        departments.add("财务部");

        // 使用迭代器遍历
        SimpleList<String>.Iterator it = departments.iterator();
        System.out.println("===== 飞翔科技部门列表 =====");
        while (it.hasNext()) {
            System.out.println("  • " + it.next());
        }

        System.out.println();
        System.out.println("迭代器内部类全限定名: " + Iterator.class.getName());
        // 输出: SimpleList$Iterator

        // 验证内部类持有外部类引用
        System.out.println("内部类是否包含外部类引用字段 this$0: "
            + Iterator.class.getDeclaredFields()[0].getName());
    }
}
运行输出:
===== 飞翔科技部门列表 =====
  • 技术部
  • 市场部
  • 人力资源部
  • 财务部

迭代器内部类全限定名: SimpleList$Iterator
内部类是否包含外部类引用字段 this$0: this$0

易错场景

反例一:在静态方法中直接 new 内部类

小崔写完 Employee 类后,想在 main 方法中直接创建 Address:

// ❌ 错误
public class Employee {
    private String name = "飞翔员工";

    public class Address {
        public void print() {
            System.out.println(name);
        }
    }

    public static void main(String[] args) {
        // 编译错误!静态方法中没有外部类实例
        Address addr = new Address();  // ❌
    }
}

编译错误:

No enclosing instance of type Employee is accessible. Must qualify the allocation
with an enclosing instance of type Employee (e.g. x.new A() where x is an instance of Employee).

纠正:

// ✅ 正确
public static void main(String[] args) {
    Employee emp = new Employee();
    Address addr = emp.new Address();  // 必须先有外部类对象
    addr.print();
}

反例二:内部类与外部类同名成员导致的混淆

// ❌ 容易混淆
public class Outer {
    private String name = "Outer";

    public class Inner {
        private String name = "Inner";

        public void print() {
            String name = "Local";
            System.out.println(name);           // "Local" —— 局部变量
            System.out.println(this.name);      // "Inner" —— 内部类字段
            System.out.println(Outer.this.name);// "Outer" —— 外部类字段
        }
    }
}

关键规则:

  • name → 最近的局部变量
  • this.name → 内部类的实例字段
  • Outer.this.name → 外部类的实例字段

反例三:内部类导致外部类无法被 GC 回收

内部类持有外部类的强引用(this$0),如果内部类对象存活,外部类对象也无法被回收:

// ❌ 潜在内存泄漏
public class Activity {  // 模拟 Android Activity
    private String largeData = new String(new char[10000000]);  // 大数据

    public class ClickHandler {
        public void onClick() {
            System.out.println("点击事件,访问外部类: " + largeData);
        }
    }

    public ClickHandler getHandler() {
        return new ClickHandler();
    }
}

// ClickHandler 对象存活 → 持有 this$0 引用 → Activity 无法 GC → 内存泄漏

纠正:如果不需访问外部类成员,应使用静态内部类;如果需要,应在外部类销毁时清空内部类引用。


面试考点

Q1:成员内部类如何访问外部类的私有成员?

编译器为外部类自动生成静态桥接方法(如 access$000),将外部类的私有成员暴露给内部类。同时内部类持有外部类的引用 this$0,通过 this$0 调用这些桥接方法。这不是 JVM 层面的特权,而是编译器层面的"语法糖"。

Q2:为什么创建成员内部类对象必须先有外部类对象?

因为成员内部类的每个实例都隐式绑定到一个外部类实例。编译后的内部类构造方法会接收一个外部类参数:Inner(Outer this$0),这个引用在构造时必须提供。语法上的体现就是 outer.new Inner()。

Q3:内部类的 .class 文件命名规则是什么?

成员内部类和静态内部类:Outer$Inner.class。局部内部类:Outer$1Inner.class(数字保证唯一性)。匿名内部类:Outer$1.class(按出现顺序编号)。$ 是合法的 Java 标识符字符,但规范不推荐手动使用。

Q4:成员内部类可以声明 static 方法和 static 字段吗?

不可以。成员内部类依赖于外部类实例,所以不能有静态初始化块、静态方法或静态字段(除非静态字段是编译期常量 static final)。这一限制在 JDK 16 中被放宽,但 JDK 8 中严格禁止。

Q5:Outer.this 是什么?在什么场景下使用?

Outer.this 是在内部类中获取外部类实例引用的语法。当内部类中有与外部类同名的成员时,用来消除歧义。在多级嵌套内部类中尤为有用:A.this、B.this 分别指代不同层级的外部类。

上一页
枚举
下一页
局部内部类