内部类
一、内部类概述
内部类(Inner Class)是定义在另一个类内部的类。Java 中的内部类可以访问外部类的成员变量和方法,它使得代码更加灵活和简洁。
1.1 内部类的四种形式
public class OuterClass {
private String outerField = "outer";
private static String staticField = "static outer";
// ========== 1. 成员内部类(非静态内部类) ==========
// 特点:持有外部类的引用(OuterClass.this)
class InnerClass {
private String innerField = "inner";
// ❌ static String staticInner = "error"; // 不能有静态成员(除编译时常量)
static final int CONSTANT = 100; // ✅ 编译时常量例外
public void access() {
// 可访问外部类的所有成员(包括 private)
System.out.println(outerField); // ✅
System.out.println(staticField); // ✅
// 显式引用外部类实例
System.out.println(OuterClass.this.outerField);
// 调用外部类方法
OuterClass.this.outerMethod();
}
// 内部类与外部类同名变量的区分
public void shadowTest() {
String outerField = "local"; // 局部变量
System.out.println(outerField); // local
System.out.println(this.innerField); // inner
System.out.println(OuterClass.this.outerField); // outer
}
}
// ========== 2. 静态内部类(嵌套类) ==========
// 特点:不持有外部类引用,相当于顶层类
static class StaticInnerClass {
private String staticInnerField = "static inner";
private static String staticField2 = "static"; // ✅ 可有静态成员
public void access() {
// ❌ System.out.println(outerField); // 编译错误:无法访问非静态成员
System.out.println(staticField); // ✅ 可访问外部类静态成员
// 需要外部类实例才能访问非静态成员
OuterClass outer = new OuterClass();
System.out.println(outer.outerField); // ✅
}
public static void staticMethod() {
System.out.println("static inner method");
}
}
// ========== 3. 局部内部类(方法内定义) ==========
public void methodWithLocalInner() {
final int finalVar = 10; // 显式 final
int effectivelyFinalVar = 20; // JDK8+ 隐式 final(不能修改)
// effectivelyFinalVar = 30; // ❌ 修改后,内部类无法访问
// 局部内部类:只在方法内可见
class LocalInner {
private String localInnerField = "local inner";
public void access() {
// 可访问:外部类成员 + 方法参数 + effectively final 局部变量
System.out.println(outerField); // ✅ 外部类成员
System.out.println(finalVar); // ✅ final 变量
System.out.println(effectivelyFinalVar); // ✅ effectively final
System.out.println(localInnerField); // ✅ 自身成员
}
}
LocalInner local = new LocalInner();
local.access();
// ❌ 局部内部类在方法外不可见
// LocalInner error = new LocalInner(); // 编译错误
}
// ========== 4. 匿名内部类(常用于回调) ==========
public void anonymousExample() {
// 4.1 实现接口
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println(outerField); // ✅ 访问外部类
}
};
// 4.2 继承类
Thread t = new Thread() {
@Override
public void run() {
System.out.println("anonymous thread");
}
};
// 4.3 带初始化块的匿名类
List<String> list = new ArrayList<String>() {{
add("item1"); // 实例初始化块
add("item2");
}};
// 4.4 JDK8+ Lambda 替代(只适用于函数式接口)
Runnable r2 = () -> System.out.println(outerField); // ✅ 更简洁
}
private void outerMethod() {
System.out.println("outer method");
}
// ========== 创建内部类实例 ==========
public static void main(String[] args) {
OuterClass outer = new OuterClass();
// 1. 成员内部类:需要外部类实例
InnerClass inner = outer.new InnerClass(); // ✅
inner.access();
// 2. 静态内部类:不需要外部类实例
StaticInnerClass staticInner = new OuterClass.StaticInnerClass(); // ✅
staticInner.access();
StaticInnerClass.staticMethod(); // 直接调用静态方法
// 3. 局部内部类:只能在方法内创建
outer.methodWithLocalInner();
// 4. 匿名内部类:在创建处使用
outer.anonymousExample();
}
}
二、核心面试题详解
2.1 为什么局部内部类访问的局部变量必须是 01-final关键字?
public class FinalVariableDemo {
/**
* ❓ 问题:为什么局部内部类捕获的局部变量必须是 final(或 effectively final)?
*
* ✅ 答案:变量生命周期不一致 + 值拷贝机制
*/
public Runnable createRunnable() {
int localVar = 100; // 栈内存,方法结束后销毁
// 局部内部类
class LocalRunnable implements Runnable {
@Override
public void run() {
// ⚠️ 此时 localVar 可能已经被销毁!
System.out.println(localVar); // 如何访问?
}
}
return new LocalRunnable();
}
/**
* 底层原理:编译器生成的字节码
*
* javac 编译后生成两个 class 文件:
* - FinalVariableDemo.class
* - FinalVariableDemo$1LocalRunnable.class
*/
// 反编译后的内部类(简化版)
class LocalRunnable_Generated implements Runnable {
final int val$localVar; // ✅ 编译器添加的合成字段
LocalRunnable_Generated(int capturedVar) {
this.val$localVar = capturedVar; // 构造时复制值
}
@Override
public void run() {
System.out.println(val$localVar); // 使用拷贝的值
}
}
/**
* 为什么必须 final?
*
* 1️⃣ 值拷贝机制:
* - localVar 是方法栈上的变量
* - 内部类对象在堆上,生命周期可能比方法长
* - 编译器将 localVar 的值拷贝到内部类的字段中
*
* 2️⃣ 保持一致性:
* 如果允许修改 localVar,会出现:
* - 方法内看到的是修改后的值
* - 内部类看到的是拷贝的旧值
* - 造成数据不一致!
*
* 3️⃣ 解决方案:强制 final
* - final 变量不能修改
* - 保证内部类和外部方法看到的值一致
*/
// ========== 示例:违反 final 规则 ==========
public void demonstrateError() {
int count = 0;
Runnable r = new Runnable() {
@Override
public void run() {
// ❌ 编译错误:Variable 'count' is accessed from within inner class, needs to be final or effectively final
// System.out.println(count);
}
};
count++; // ⚠️ 修改后,count 不再是 effectively final
// 解决方案 1:使用 final 包装类
final int[] counter = {0}; // 数组引用是 final,但元素可变
Runnable r2 = new Runnable() {
@Override
public void run() {
System.out.println(counter[0]); // ✅
counter[0]++; // ✅ 修改数组元素
}
};
// 解决方案 2:使用原子类
final AtomicInteger atomicCount = new AtomicInteger(0);
Runnable r3 = () -> {
System.out.println(atomicCount.get()); // ✅
atomicCount.incrementAndGet(); // ✅
};
}
/**
* JDK 8 新特性:effectively final
*
* - JDK 7 及之前:必须显式声明 final
* - JDK 8 及之后:未显式 final,但未被修改,视为 effectively final
*/
public void effectivelyFinalDemo() {
int jdk7Var = 10; // 隐式 final(未修改)
Runnable r = () -> {
System.out.println(jdk7Var); // ✅ JDK8+ 允许
};
// jdk7Var = 20; // ❌ 修改后编译失败
}
}
2.2 成员内部类 vs 静态内部类
public class InnerClassComparison {
private String outerField = "outer";
private static String staticField = "static";
// ========== 成员内部类 ==========
class MemberInner {
// 特点 1:隐式持有外部类引用
public void showReference() {
System.out.println(OuterClass.this); // OuterClass@hashcode
}
// 特点 2:可访问外部类所有成员
public void accessOuter() {
System.out.println(outerField); // ✅
System.out.println(staticField); // ✅
}
// 特点 3:不能有静态成员(除常量)
// static int staticVar = 1; // ❌ 编译错误
static final int CONSTANT = 1; // ✅ 编译时常量
// JDK 16+ 新特性:允许静态成员
// static void staticMethod() {} // ✅ JDK 16+
}
// ========== 静态内部类 ==========
static class StaticInner {
// 特点 1:不持有外部类引用
// 无法访问 OuterClass.this
// 特点 2:只能访问外部类静态成员
public void accessOuter() {
// System.out.println(outerField); // ❌
System.out.println(staticField); // ✅
}
// 特点 3:可以有静态成员
static int staticVar = 1; // ✅
static void staticMethod() {} // ✅
}
// ========== 内存占用对比 ==========
public static void memoryTest() {
OuterClass outer = new OuterClass();
// 成员内部类:持有外部类引用
MemberInner member = outer.new MemberInner();
// 内存结构:
// MemberInner {
// this$0 -> OuterClass // 隐藏字段,4-8 字节
// ... 其他字段
// }
// 静态内部类:无额外引用
StaticInner staticInner = new StaticInner();
// 内存结构:
// StaticInner {
// ... 仅自身字段
// }
/**
* 内存泄漏风险:
*
* 如果 MemberInner 生命周期长于 OuterClass,
* 会导致 OuterClass 无法被 GC 回收!
*/
}
// ========== 使用场景对比 ==========
/**
* 使用成员内部类:
* 1. 需要访问外部类实例状态
* 2. 如:迭代器、事件监听器
*/
class Iterator {
private int cursor = 0;
public boolean hasNext() {
return cursor < outerField.length(); // ✅ 访问外部类
}
}
/**
* 使用静态内部类:
* 1. 不需要访问外部类实例
* 2. 如:Builder 模式、辅助类
* 3. 优先使用(避免内存泄漏)
*/
static class Builder {
private String field1;
private String field2;
public Builder setField1(String field1) {
this.field1 = field1;
return this;
}
public OuterClass build() {
OuterClass instance = new OuterClass();
instance.outerField = this.field1;
return instance;
}
}
}
2.3 内部类的字节码分析
public class BytecodeAnalysis {
private String outer = "outer";
class Inner {
public void print() {
System.out.println(outer);
}
}
/**
* 编译后生成的文件:
* - BytecodeAnalysis.class
* - BytecodeAnalysis$Inner.class
*
* javap -c -p BytecodeAnalysis$Inner.class 反编译结果(简化):
*/
/*
class BytecodeAnalysis$Inner {
// 1️⃣ 编译器生成的合成字段(持有外部类引用)
final BytecodeAnalysis this$0;
// 2️⃣ 编译器生成的构造器(传入外部类引用)
BytecodeAnalysis$Inner(BytecodeAnalysis outer) {
this.this$0 = outer;
}
// 3️⃣ print 方法的字节码
public void print() {
// 实际访问:this$0.outer
System.out.println(this.this$0.outer);
}
}
*/
/**
* 关键点:
* - this$0 是编译器自动添加的 synthetic 字段
* - final 修饰,防止被修改
* - 构造时传入外部类实例
*/
// ========== 多层嵌套的内部类 ==========
class Level1 {
private String level1Field = "L1";
class Level2 {
private String level2Field = "L2";
class Level3 {
public void access() {
// 访问外层类需要多次跳转
System.out.println(outer); // BytecodeAnalysis.this.outer
System.out.println(level1Field); // Level1.this.level1Field
System.out.println(level2Field); // Level2.this.level2Field
System.out.println(Level1.this.level1Field); // 显式访问
}
}
}
}
/**
* 反编译 Level3.class:
*
* class BytecodeAnalysis$Level1$Level2$Level3 {
* final BytecodeAnalysis$Level1$Level2 this$2; // 持有 Level2 引用
*
* BytecodeAnalysis$Level1$Level2$Level3(BytecodeAnalysis$Level1$Level2 level2) {
* this.this$2 = level2;
* }
*
* public void access() {
* // 访问 outer:this$2.this$1.this$0.outer
* // 访问 level1Field:this$2.this$1.level1Field
* // 访问 level2Field:this$2.level2Field
* }
* }
*
* ⚠️ 性能问题:多层嵌套导致多次间接访问
*/
}
三、实战应用场景
3.1 迭代器模式(经典应用)
/**
* 自定义容器的迭代器实现
* 成员内部类的最佳实践
*/
public class MyArrayList<E> {
private Object[] elements;
private int size;
public MyArrayList() {
elements = new Object[10];
}
public void add(E e) {
elements[size++] = e;
}
// ========== 迭代器(成员内部类) ==========
public Iterator<E> iterator() {
return new Itr(); // 返回内部类实例
}
private class Itr implements Iterator<E> {
int cursor = 0; // 当前位置
int lastRet = -1; // 上次返回的位置
int expectedModCount = modCount; // 快速失败检测
@Override
public boolean hasNext() {
return cursor != size; // ✅ 访问外部类的 size
}
@Override
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = MyArrayList.this.elements; // ✅ 访问外部类数组
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
@Override
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
MyArrayList.this.remove(lastRet); // ✅ 调用外部类方法
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
// 修改计数(用于快速失败)
private transient int modCount = 0;
public void remove(int index) {
modCount++;
// 删除逻辑...
}
/**
* 为什么迭代器使用成员内部类?
*
* 1️⃣ 需要访问外部类的 private 成员(elements、size)
* 2️⃣ 逻辑紧密耦合,不需要暴露给外部
* 3️⃣ 每个容器实例有独立的迭代器状态
*/
}
// 使用示例
MyArrayList<String> list = new MyArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
// it.remove(); // 支持安全删除
}
3.2 Builder 模式(静态内部类)
/**
* 建造者模式的标准实现
* 静态内部类的典型应用
*/
public class User {
// 必填字段
private final String username;
private final String password;
// 可选字段
private final String email;
private final String phone;
private final int age;
private final String address;
// ========== 私有构造器,只能通过 Builder 创建 ==========
private User(Builder builder) {
this.username = builder.username;
this.password = builder.password;
this.email = builder.email;
this.phone = builder.phone;
this.age = builder.age;
this.address = builder.address;
}
// ========== Builder(静态内部类) ==========
public static class Builder {
// 必填字段
private final String username;
private final String password;
// 可选字段(默认值)
private String email = "";
private String phone = "";
private int age = 0;
private String address = "";
// 必填字段通过构造器传入
public Builder(String username, String password) {
this.username = username;
this.password = password;
}
// 可选字段通过链式调用设置
public Builder email(String email) {
this.email = email;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public Builder age(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Invalid age");
}
this.age = age;
return this;
}
public Builder address(String address) {
this.address = address;
return this;
}
// 构建最终对象
public User build() {
// 可在此处添加验证逻辑
if (username == null || username.isEmpty()) {
throw new IllegalStateException("Username is required");
}
return new User(this);
}
}
/**
* 为什么 Builder 使用静态内部类?
*
* 1️⃣ 不需要访问外部类实例(User 未创建前就要使用 Builder)
* 2️⃣ 逻辑内聚,Builder 只为 User 服务
* 3️⃣ 避免内存泄漏(无外部类引用)
* 4️⃣ 可以访问外部类的 private 构造器
*/
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", email='" + email + '\'' +
", phone='" + phone + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
// 使用示例
User user = new User.Builder("admin", "123456")
.email("admin@example.com")
.phone("1234567890")
.age(25)
.address("Beijing")
.build();
System.out.println(user);
/**
* Builder 模式优势:
* 1. 参数众多时避免构造器膨胀
* 2. 可读性强(链式调用)
* 3. 不可变对象(所有字段 final)
* 4. 参数校验集中在 build() 方法
*/
3.3 事件监听器(匿名内部类)
/**
* GUI 编程中的事件处理
* 匿名内部类的经典场景
*/
public class ButtonDemo {
public static void main(String[] args) {
JFrame frame = new JFrame("Button Demo");
JButton button = new JButton("Click Me");
// ========== 方式 1:匿名内部类(JDK 7) ==========
button.addActionListener(new ActionListener() {
private int clickCount = 0; // 内部状态
@Override
public void actionPerformed(ActionEvent e) {
clickCount++;
System.out.println("Clicked " + clickCount + " times");
// ✅ 可访问外部变量(effectively final)
System.out.println("Button: " + button.getText());
}
});
// ========== 方式 2:Lambda 表达式(JDK 8+) ==========
// ⚠️ Lambda 无法定义实例变量
AtomicInteger count = new AtomicInteger(0); // 替代方案
button.addActionListener(e -> {
count.incrementAndGet();
System.out.println("Clicked " + count.get() + " times");
});
// ========== 方式 3:方法引用(JDK 8+) ==========
button.addActionListener(System.out::println);
frame.add(button);
frame.setSize(300, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
/**
* 匿名内部类 vs Lambda:
*
* 匿名内部类:
* ✅ 可以有实例变量和初始化块
* ✅ 可以实现多个方法(非函数式接口)
* ✅ 可以覆盖 Object 方法(toString/equals/hashCode)
* ❌ 语法冗长
*
* Lambda:
* ✅ 语法简洁
* ✅ 无额外类文件(invokedynamic)
* ❌ 只能用于函数式接口
* ❌ 无法有实例状态
*/
}
3.4 单例模式(静态内部类)
/**
* 静态内部类实现单例模式
* 线程安全 + 延迟加载
*/
public class Singleton {
// 私有构造器
private Singleton() {
// 防止反射攻击
if (Holder.INSTANCE != null) {
throw new IllegalStateException("Singleton already initialized");
}
}
// ========== 静态内部类(延迟初始化持有者) ==========
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
// 获取单例
public static Singleton getInstance() {
return Holder.INSTANCE; // 首次调用时才加载 Holder 类
}
/**
* 为什么使用静态内部类?
*
* 1️⃣ 延迟加载:
* - Singleton 类加载时,Holder 不会加载
* - 首次调用 getInstance() 时才加载 Holder
* - 利用 JVM 类加载机制保证线程安全
*
* 2️⃣ 线程安全:
* - JVM 保证类初始化过程的线程安全
* - 无需 synchronized
*
* 3️⃣ 高性能:
* - 无锁竞争
* - 无 volatile 开销
*
* 4️⃣ 对比其他方案:
* - 饿汉式:类加载时初始化,无延迟加载
* - DCL(双检锁):需要 volatile,复杂易错
* - 枚举:最佳方案,但语义不直观
*/
// 业务方法
public void doSomething() {
System.out.println("Singleton instance: " + this);
}
}
// 使用示例
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2); // true
/**
* 类加载时机验证:
*/
class LoadingTest {
static {
System.out.println("Singleton class loaded");
}
static class Holder {
static {
System.out.println("Holder class loaded");
}
static final Singleton INSTANCE = new Singleton();
}
public static void main(String[] args) {
System.out.println("Main start");
// 输出:Main start
// Singleton class loaded(Singleton 类被引用)
Singleton instance = Holder.INSTANCE;
// 输出:Holder class loaded(首次访问 Holder 才加载)
}
}
⬅️ java中的位运算技巧 🏠 00-Java ➡️ 03-JDK9-21新特性速览
💬 评论