你有没有遇到过这样的代码:
public class Outer {
class Inner { }
}
看到 class Inner
写在另一个类里面,心里一懵:
“这合法吗?”
“它有啥用?”
“面试官要是问‘说说内部类’,我咋答?”
不讲虚的,只讲“你什么时候会用到它”、“它解决了什么问题”、
简单说,内部类就是定义在另一个类里面的类。
就像你家有个“主卧”,主卧里还放了个“保险箱”—— 这个“保险箱”只属于这个房间,别人用不了,也看不到。
public class Outer { // 外部类:主卧
private String data = "秘密";
class Inner { // 内部类:保险箱
public void print() {
System.out.println(data); // 可以直接访问外部类的私有成员!
}
}
}
✅ 内部类最大的特点:它可以访问外部类的私有成员,包括字段和方法。
内部类可以隐藏在外部类中,不对外暴露,适合实现“只给自家用”的逻辑。
相关类写在一起,逻辑清晰,避免类文件满天飞。
Java 不支持多继承(不能 extends 多个类),但一个内部类可以继承一个类,外部类继承另一个类,变相实现“多继承”。
定义在类中,不加 static
。
public class Outer {
private String name = "张三";
public class Inner {
public void display() {
System.out.println("我是" + name); // 直接访问外部类私有字段
}
}
}
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner(); // 注意!不是 new Outer.Inner()
inner.display();
“成员内部类常用于事件监听、GUI 编程,比如一个窗口类里定义一个按钮点击处理器,逻辑内聚,封装性好。”
加了 static
的内部类,叫静态内部类。
它不依赖外部类的实例,可以直接创建。
public class Outer {
private static String company = "阿里巴巴";
public static class StaticInner {
public void work() {
System.out.println(company + "上班"); // 只能访问静态成员
}
}
}
Outer.StaticInner worker = new Outer.StaticInner();
worker.work();
“我们用
Result<T>
类里定义一个静态内部类Error
,表示错误信息,调用方用Result.Error
就能访问,清晰又方便。”
定义在方法或代码块中,只能在该方法内使用。
public class Outer {
public void doSomething() {
final String msg = "局部类只能用final或有效final变量";
class LocalInner {
public void print() {
System.out.println(msg); // 可以访问方法内的final变量
}
}
new LocalInner().print();
}
}
public
、private
等访问修饰符。final
或“有效 final”(值没变过)的局部变量。“局部内部类适合定义只在某个方法中使用的辅助类,避免类污染全局命名空间。”
没有名字的内部类,通常用于实现接口或继承类,且只用一次。
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程启动!");
}
}).start();
new 接口/类
。“以前写 Swing 或 Android 时,按钮点击事件全是匿名内部类。现在 Java 8 后,很多被 Lambda 取代了。”
从 Java 8 开始,对于函数式接口(只有一个抽象方法的接口),可以用 Lambda 替代匿名内部类。
// 以前:匿名内部类
new Thread(new Runnable() {
public void run() {
System.out.println("Hello");
}
}).start();
// 现在:Lambda
new Thread(() -> System.out.println("Hello")).start();
✅ Lambda 更简洁,但本质还是会生成一个类(可能是内部类,也可能是 invokedynamic)。
你可能会问: “内部类为啥能访问外部类的私有字段?这不破坏封装了吗?”
答案是:编译器帮你偷偷加了‘桥接方法’。
比如:
class Outer {
private String secret = "123";
class Inner {
void access() {
System.out.println(secret); // 访问私有字段
}
}
}
编译后,编译器会生成一个包级私有的桥接方法:
// 编译器自动生成(你看不到)
String Outer.access$000(Outer outer) {
return outer.secret;
}
然后内部类通过调用这个方法来访问私有字段。
💡 所以:语法上封装了,但编译器为了实现功能,做了妥协。
答: Java 有 4 种内部类:
Map.Entry
;Result<T>
里定义 Error
类。区别 | 成员内部类 | 静态内部类 |
---|---|---|
是否依赖外部类实例 | 是(必须先有外部类对象) | 否(可以直接 new) |
能否访问外部类非静态成员 | 能 | 不能 |
是否持有外部类引用 | 是(可能导致内存泄漏) | 否 |
使用场景 | 强关联逻辑 | 工具类、配置类 |
答: 因为内部类可能在方法结束后才执行(比如线程),而局部变量在栈上,方法结束就销毁了。 加
final
后,编译器会把变量“复制”一份到内部类中,保证数据安全。 Java 8 后叫“有效 final”,只要没改过,就当作 final 处理。
答: 成员内部类会!因为它隐式持有外部类的引用。 如果你在 Activity 或 Fragment 里定义了非静态内部类,并被长时间持有(如线程、单例),就会导致外部类无法被回收,引发内存泄漏。 解决方案:
类型 | 是否静态 | 依赖外部实例 | 访问非静态成员 | 典型用途 |
---|---|---|---|---|
成员内部类 | 否 | 是 | 是 | 事件处理器、强关联逻辑 |
静态内部类 | 是 | 否 | 否 | 工具类、配置类、Result 封装 |
局部内部类 | 否 | 是 | 是(仅final) | 方法内临时使用 |
匿名内部类 | 否 | 是 | 是(仅final) | 回调、线程、监听 |
Lambda | - | 否(函数式接口) | 是(有效final) | 替代匿名内部类 |
内部类不是“炫技”,而是“解耦与封装”的利器。 用得好,代码更清晰; 用不好,容易引发内存泄漏。 关键是:知道每种内部类的适用场景,不滥用。
希望这篇能帮你彻底搞懂 Java 内部类!