Java 内部类

背景与动机#

内部类是定义在另一个类内部的类。它仍然是外部类的成员,可以直接访问外部类的私有成员,也常用于回调、Builder、只服务某个外部类的辅助类型。

学内部类时,先分清四件事:

  • 它是静态的还是非静态的
  • 它定义在外部类的什么位置
  • 创建内部类对象时,外部类实例是否必须存在
  • 它和外部类、局部变量同名时,变量到底指向谁

核心原理拆解#

1. 内部类是什么#

class Outer {
private int value = 10;
class Inner {
void print() {
System.out.println(value);
}
}
}

Inner 写在 Outer 里面,属于 Outer 的成员之一。成员内部类可以直接访问外部类的实例成员,包括 private 字段。

2. 四种内部类#

类型定义位置是否依赖外部类实例典型创建方式
静态内部类类中,带 staticnew Outer.StaticInner()
成员内部类类中,非 staticouter.new Inner()
局部内部类方法或代码块中看所在方法方法内 new Inner()
匿名内部类表达式位置看上下文new Interface() { ... }

img
img

3. 静态内部类#

class Outer {
private static int count = 0;
static class Counter {
void show() {
System.out.println(count);
}
}
}
Outer.Counter counter = new Outer.Counter();

特点:

  • 不持有外部类实例引用
  • 只能直接访问外部类的静态成员
  • 创建时不需要先有 Outer 对象

它和 Java static 关键字 的关系很紧,可以把它理解成“挂在类上的嵌套类”。

img
img

4. 成员内部类#

Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.print();

特点:

  • 每个成员内部类对象都隐式关联一个外部类实例
  • 可以访问外部类的静态和非静态成员
  • 内部类里可通过 Outer.this 访问外部类当前对象

img
img

5. 局部内部类#

class Outer {
void demo() {
class Local {
void hello() {
System.out.println("local");
}
}
new Local().hello();
}
}

特点:

  • 只在定义它的方法或代码块内可见
  • 定义在实例方法里时,可访问外部类实例成员
  • 定义在静态方法里时,只能访问外部类静态成员

img
img

6. 匿名内部类#

Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("run");
}
};

特点:

  • 没有类名,必须继承一个类或实现一个接口
  • 适合一次性回调,比如旧版 GUI 监听、Comparator
  • 不能定义静态成员(Java 8 前规则更严,现代代码更推荐 Lambda)

img
img

7. 为什么局部变量要 effectively final#

class Outer {
void demo() {
int n = 10;
Runnable r = () -> System.out.println(n);
}
}

局部变量存在栈上,方法结束后就销毁;内部类或 Lambda 可能晚于方法结束才执行。Java 要求捕获的局部变量在赋值后不能再改,避免“外部变量已经没了,内部还在用旧值”的生命周期问题。

Java 8 起不要求手写 final,但变量必须是 effectively final(赋值后不再修改)。

8. 同名变量怎么区分#

class Outer {
private int age = 12;
class Inner {
private int age = 13;
void print() {
int age = 14;
System.out.println(age); // 14,局部变量
System.out.println(this.age); // 13,内部类成员
System.out.println(Outer.this.age); // 12,外部类成员
}
}
}

优先级:局部变量 > 内部类成员 > 外部类成员

最小可运行代码示例#

public class InnerClassDemo {
public static void main(String[] args) {
Outer outer = new Outer();
outer.new Inner().print();
Outer.StaticHelper helper = new Outer.StaticHelper();
helper.show();
}
}
class Outer {
private int value = 100;
private static int staticValue = 200;
class Inner {
void print() {
System.out.println(value);
}
}
static class StaticHelper {
void show() {
System.out.println(staticValue);
}
}
}

常见陷阱与错误示例#

1. 把成员内部类当成静态内部类创建#

// 编译错误:成员内部类不能 Outer.Inner 这样直接 new
// Outer.Inner inner = new Outer.Inner();

成员内部类必须先有外部类实例:outer.new Inner()

2. 在静态方法里直接 new 非静态内部类#

静态方法里没有“当前外部类对象”,不能直接创建依赖外部类实例的成员内部类。

3. 匿名内部类滥用#

逻辑复杂、需要复用或测试的类型,应写成独立类或 Lambda,不要堆在匿名内部类里。

面试高频问题#

1. Java 内部类有哪几种#

静态内部类、成员内部类、局部内部类、匿名内部类。

2. 静态内部类和成员内部类区别#

静态内部类不依赖外部类实例,只能直接访问外部类静态成员;成员内部类依赖外部类实例,可访问外部类所有成员。

3. 为什么内部类访问局部变量时变量要 effectively final#

因为局部变量生命周期短,内部类可能更晚执行;限制不可变,是为了保证捕获的是稳定拷贝值。

4. 内部类有什么优点#

能访问外部类私有成员、增强封装、实现回调、把只服务外部类的辅助逻辑收在一起。

一句话总结#

内部类的核心不是语法花样,而是“嵌套类型与外部类怎么绑定”:静态内部类绑定类,成员内部类绑定外部类实例,局部/匿名内部类则常用于临时逻辑和回调。

文章目录

文章目录