目录
什么是内部类?
内部类就是定义在另一个类内部的类。写在成员位置的,属于外部类的成员。
想象一下我们的日常生活:一台电脑包含CPU、内存、硬盘等组件。从面向对象的角度看,我们可以将电脑看作一个类(Computer
),而CPU、内存等组件则可以看作电脑内部的类(CPU
, Memory
, HardDisk
)。这些组件属于电脑,在电脑内部工作,与电脑有紧密的联系。
这就像Java中的内部类概念:一个类定义在另一个类的内部,形成一种"类中有类"的结构。
生活中的内部类例子
// 电脑类
public class Computer {
private String brand;
private double price;
// CPU内部类
class CPU {
private String model;
private int cores;
public void run() {
// CPU可以访问电脑的品牌信息
System.out.println(brand + "电脑的" + model + "处理器正在运行...");
}
}
// 显卡内部类
class GraphicsCard {
private String model;
private int memory;
public void display() {
// 显卡也可以访问电脑的信息
System.out.println(brand + "电脑的" + model + "显卡正在渲染画面...");
}
}
// 电脑使用内部组件
public void start() {
CPU cpu = new CPU();
cpu.model = "Intel i7";
cpu.run();
GraphicsCard gc = new GraphicsCard();
gc.model = "NVIDIA RTX 3080";
gc.display();
}
}
在这个例子中:
Computer
是外部类,代表整台电脑CPU
和GraphicsCard
是内部类,代表电脑的组件- 内部类可以访问外部类的属性(
brand
) - 外部类可以直接创建和使用内部类
这种设计反映了现实世界中的"整体-部分"关系,内部类就像是外部类的一个组成部分。
public class Outer { // 外部类
private int num = 10;
class Inner { // 内部类
// 内部类的代码
}
}
为什么需要内部类?
理解内部类存在的原因,可以通过更多的生活例子来理解:
生活中的例子
汽车与发动机
- 汽车(Car)是外部类,发动机(Engine)是内部类
- 发动机是汽车的核心组件,与汽车紧密相关
- 发动机需要访问汽车的各种状态(油量、温度等)
- 一般不会将发动机单独使用,它主要为汽车服务
手机与应用程序
- 手机(Phone)是外部类,应用程序(App)是内部类
- 应用程序需要访问手机的功能(摄像头、存储空间等)
- 应用程序主要为手机提供特定功能
内部类的存在意义
提高封装性
- 内部类可以访问外部类的所有成员,包括私有成员
- 内部类本身可以对外隐藏,只有外部类能访问它
- 实现高内聚、低耦合的设计理念
实现多重继承
- Java不支持类的多重继承,但内部类可以继承其他类
- 通过在一个类中创建多个内部类,每个内部类继承不同的类,实现类似多重继承的效果
更好地实现回调机制
- 匿名内部类特别适合用于事件处理和回调机制
- 简化了接口实现的代码结构
隐藏实现细节
- 将实现细节隐藏在外部类内部,对外只暴露必要的接口
- 如集合框架中的迭代器实现,就是通过内部类完成的
组织逻辑上紧密相关的类
- 当一个类只对另一个类有用时,将其定义为内部类可以更好地组织代码
- 体现了"has-a"关系中的"a"是专属于宿主对象的情况
内部类的分类
Java中的内部类主要分为四种类型:
- 成员内部类:写在类成员位置的内部类
- 静态内部类:使用static修饰的内部类
- 局部内部类:定义在方法中的内部类
- 匿名内部类:没有名字的内部类,隐藏了类的名字
1. 成员内部类
什么是成员内部类?
成员内部类是定义在类成员位置的内部类,就像一个普通的成员变量一样。
成员内部类的特点
- 可以使用外部类的所有成员和方法,即使是private的
- 在JDK 16之前,成员内部类中不能定义静态成员
- 成员内部类可以被访问修饰符修饰:
private
、default
、protected
、public
- 成员内部类不能脱离外部类对象独立存在,需要先创建外部类对象
如何使用成员内部类?
方式一:在外部类中直接创建内部类对象并使用
public class Outer {
private int num = 10;
// 成员内部类
class Inner {
public void show() {
System.out.println("外部类成员变量num=" + num);
}
}
// 在外部类中使用内部类
public void method() {
Inner i = new Inner();
i.show();
}
}
方式二:在其他类中使用成员内部类
public class Test {
public static void main(String[] args) {
// 先创建外部类对象
Outer outer = new Outer();
// 再创建内部类对象
Outer.Inner inner = outer.new Inner();
// 使用内部类方法
inner.show();
}
}
成员内部类访问外部类同名成员
如果内部类和外部类有同名的成员变量,可以使用外部类名.this.成员变量
来访问外部类的成员:
public class Outer {
private int num = 10;
class Inner {
private int num = 20;
public void show() {
int num = 30;
System.out.println("局部变量:" + num); // 30
System.out.println("内部类成员变量:" + this.num); // 20
System.out.println("外部类成员变量:" + Outer.this.num); // 10
}
}
}
2. 静态内部类
什么是静态内部类?
静态内部类是使用static
关键字修饰的内部类,是一种特殊的成员内部类。
静态内部类的特点
- 静态内部类只能访问外部类的静态成员,不能直接访问非静态成员
- 静态内部类可以包含静态成员,也可以包含非静态成员
- 创建静态内部类对象时,不需要依赖外部类对象
静态内部类的使用
创建静态内部类对象的格式:
外部类名.内部类名 对象名 = new 外部类名.内部类名();
示例代码:
public class Car { // 外部类
private String carName;
private static int carAge = 10;
// 静态内部类
static class Engine {
private String engineName;
public void show() {
// 可以访问外部类静态成员
System.out.println("汽车年龄:" + carAge);
// 不能访问外部类非静态成员
// System.out.println(carName); // 编译错误
}
}
}
// 使用静态内部类
public class Test {
public static void main(String[] args) {
// 直接创建静态内部类对象,不需要外部类对象
Car.Engine engine = new Car.Engine();
engine.show();
}
}
静态内部类的方法调用
- 调用静态内部类的静态方法:
外部类名.内部类名.方法名();
- 调用静态内部类的非静态方法:先创建静态内部类对象,再用对象调用方法
public class Outer {
// 静态内部类
static class Inner {
// 静态方法
public static void staticMethod() {
System.out.println("静态内部类的静态方法");
}
// 非静态方法
public void normalMethod() {
System.out.println("静态内部类的非静态方法");
}
}
}
// 调用方法
public class Test {
public static void main(String[] args) {
// 调用静态方法
Outer.Inner.staticMethod();
// 调用非静态方法
Outer.Inner inner = new Outer.Inner();
inner.normalMethod();
}
}
3. 局部内部类
什么是局部内部类?
局部内部类是定义在方法中的类,像局部变量一样,只能在定义它的方法内部使用。
局部内部类的特点
- 只能在定义它的方法内部使用
- 可以访问外部类的所有成员
- 可以访问方法中的final或effectively final(Java 8以后)的局部变量
局部内部类的使用
public class Outer {
private int outerField = 10;
public void method() {
final int localVar = 20; // final局部变量
int effectivelyFinal = 30; // effectively final变量(不会被修改)
// 局部内部类
class LocalInner {
public void show() {
// 访问外部类成员
System.out.println("外部类成员:" + outerField);
// 访问方法中的局部final变量
System.out.println("局部变量:" + localVar);
// 访问effectively final变量
System.out.println("Effectively final变量:" + effectivelyFinal);
}
}
// 创建局部内部类对象并调用方法
LocalInner inner = new LocalInner();
inner.show();
// 注意:这里不能修改effectivelyFinal的值
// effectivelyFinal = 40; // 这样会导致编译错误
}
}
4. 匿名内部类
什么是匿名内部类?
匿名内部类是隐藏了名字的内部类,本质上是一个没有名字的局部内部类,它必须继承一个类或实现一个接口。
匿名内部类的特点
- 没有显式的类名
- 在声明的同时完成实例化
- 一般用于实现接口或继承类
- 编译后会生成
外部类名$数字.class
文件
匿名内部类的格式
new 类名或接口名() {
// 重写方法
};
匿名内部类的使用场景
当接口的实现类(或父类的子类)只使用一次时,可以使用匿名内部类简化代码。
匿名内部类示例
1. 实现接口的匿名内部类
public class Test {
public static void main(String[] args) {
// 使用匿名内部类实现Runnable接口
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("这是匿名内部类实现的run方法");
}
};
new Thread(r).start();
// 更简洁的写法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("直接创建匿名内部类");
}
}).start();
}
}
2. 继承类的匿名内部类
abstract class Animal {
public abstract void eat();
}
public class Test {
public static void main(String[] args) {
// 使用匿名内部类继承抽象类
Animal a = new Animal() {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
};
a.eat();
}
}
3. 带参数的匿名内部类
interface Calculator {
int calculate(int a, int b);
}
public class Test {
public static void main(String[] args) {
// 使用匿名内部类实现带参数的接口
Calculator c = new Calculator() {
@Override
public int calculate(int a, int b) {
return a + b;
}
};
System.out.println("计算结果:" + c.calculate(10, 20));
}
}
内部类的实际应用
1. 实现多重继承
Java不支持类的多继承,但可以通过内部类模拟实现:
class A {
public void methodA() {
System.out.println("来自A类的方法");
}
}
class B {
public void methodB() {
System.out.println("来自B类的方法");
}
}
// 通过内部类实现对A和B功能的同时使用
class C {
// 继承A的内部类
private class InnerA extends A {
public void methodA() {
super.methodA();
}
}
// 继承B的内部类
private class InnerB extends B {
public void methodB() {
super.methodB();
}
}
// 对外提供方法
public void methodA() {
new InnerA().methodA();
}
public void methodB() {
new InnerB().methodB();
}
}
2. 封装实现细节
内部类可以用来隐藏实现细节,如集合类中的迭代器实现:
public class MyArrayList<E> {
private Object[] elements;
private int size;
// 其他代码...
// 使用内部类实现迭代器
private class MyIterator implements Iterator<E> {
private int cursor;
@Override
public boolean hasNext() {
return cursor < size;
}
@Override
public E next() {
if (cursor >= size) {
throw new NoSuchElementException();
}
return (E) elements[cursor++];
}
}
// 对外提供获取迭代器的方法
public Iterator<E> iterator() {
return new MyIterator();
}
}
3. 回调机制
匿名内部类常用于实现事件监听和回调:
// 在Swing中使用匿名内部类处理按钮点击
JButton button = new JButton("点击我");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("按钮被点击了!");
}
});
内部类与Lambda表达式
在Java 8之后,对于只有一个抽象方法的接口(函数式接口),可以使用Lambda表达式代替匿名内部类,使代码更加简洁:
// 使用匿名内部类
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("使用匿名内部类");
}
};
// 使用Lambda表达式
Runnable r2 = () -> System.out.println("使用Lambda表达式");
// 启动线程
new Thread(r1).start();
new Thread(r2).start();
// 直接使用Lambda创建线程
new Thread(() -> System.out.println("直接使用Lambda")).start();
内部类编译后的文件命名规则
编译含有内部类的Java文件后,会生成多个.class文件:
- 成员内部类:
外部类名$内部类名.class
- 静态内部类:
外部类名$内部类名.class
- 局部内部类:
外部类名$数字内部类名.class
- 匿名内部类:
外部类名$数字.class
例如,如果有以下类定义:
public class Outer {
class Inner {}
static class StaticInner {}
public void method() {
class LocalInner {}
new Runnable() {
public void run() {}
};
}
}
编译后将生成以下文件:
Outer.class
Outer$Inner.class
Outer$StaticInner.class
Outer$1LocalInner.class
Outer$1.class
(匿名内部类)
内部类面试常见问题
1. 内部类的各种差异比较
特性 | 成员内部类 | 静态内部类 | 局部内部类 | 匿名内部类 |
---|---|---|---|---|
定义位置 | 类成员位置 | 类成员位置 | 方法内部 | 方法内部 |
访问修饰符 | 可以使用 | 可以使用 | 不能使用 | 不能使用 |
是否可静态 | JDK16前不可 | 可以 | 不可以 | 不可以 |
是否需要外部类对象 | 需要 | 不需要 | 需要 | 需要 |
是否可以访问外部类非静态成员 | 可以 | 不可以 | 可以 | 可以 |
是否可以访问外部类静态成员 | 可以 | 可以 | 可以 | 可以 |
2. 为什么局部内部类只能访问final局部变量?
这是因为局部变量在方法结束后就会被销毁,而局部内部类对象可能在方法结束后仍然存在。如果允许内部类修改局部变量,当变量已经被销毁后,内部类却还引用这个变量,会导致数据不一致。为了解决这个问题,Java要求局部内部类访问的局部变量必须是final的(Java 8后可以是effectively final)。
内部类与外部类的关系
内部类与外部类的关联
内部类持有外部类的引用
- 非静态内部类隐式持有外部类的引用(
Outer.this
) - 这也是为什么非静态内部类能访问外部类所有成员的原因
- 注意:这可能导致内存泄漏,当内部类对象生命周期比外部类对象长时
- 非静态内部类隐式持有外部类的引用(
编译后的实现细节
- 内部类编译后会生成独立的
.class
文件 - 非静态内部类的构造函数会隐式接收外部类的引用
- 访问外部类私有成员时,编译器会生成特殊的访问方法
- 内部类编译后会生成独立的
// 编译前的代码
public class Outer {
private int x = 10;
class Inner {
void access() {
System.out.println(x); // 访问外部类的私有成员
}
}
}
// 编译器处理后的逻辑(简化表示)
public class Outer {
private int x = 10;
// 为内部类提供的访问方法
static int access$000(Outer outer) {
return outer.x;
}
class Inner {
final Outer this$0; // 持有外部类引用
Inner(Outer outer) {
this$0 = outer; // 保存外部类引用
}
void access() {
System.out.println(Outer.access$000(this$0)); // 通过特殊方法访问
}
}
}
总结
内部类是Java中一个强大的特性,它允许我们在一个类中定义另一个类,增强了封装性和代码的组织结构。主要分为四种类型:
- 成员内部类:像普通成员一样的内部类
- 静态内部类:使用static修饰的内部类
- 局部内部类:定义在方法中的内部类
- 匿名内部类:没有名字的内部类
内部类存在的主要意义:
- 提高封装性,隐藏实现细节
- 实现类似多重继承的功能
- 更好地组织逻辑上紧密相关的类
- 简化事件处理和回调机制的实现
- 提供更灵活的访问控制
使用内部类的建议:
- 当一个类只对另一个类有用时,考虑使用内部类
- 需要访问外部类私有成员时,使用非静态内部类
- 不需要访问外部类实例成员时,优先使用静态内部类(减少内存引用)
- 仅在方法内使用的类,定义为局部内部类
- 实现接口或扩展类且只使用一次时,考虑使用匿名内部类或Lambda表达式
随着Java的发展,内部类与Lambda表达式、方法引用等新特性结合使用,可以使代码更加简洁和易读。掌握内部类是成为Java高级开发者的必备技能。