目录
什么是内部类?
生活中的内部类例子
为什么需要内部类?
生活中的例子
内部类的存在意义
内部类的分类
1. 成员内部类
什么是成员内部类?
成员内部类的特点
如何使用成员内部类?
成员内部类访问外部类同名成员
2. 静态内部类
什么是静态内部类?
静态内部类的特点
静态内部类的使用
静态内部类的方法调用
3. 局部内部类
什么是局部内部类?
局部内部类的特点
局部内部类的使用
4. 匿名内部类
什么是匿名内部类?
匿名内部类的特点
匿名内部类的格式
匿名内部类的使用场景
匿名内部类示例
内部类的实际应用
1. 实现多重继承
2. 封装实现细节
3. 回调机制
内部类与Lambda表达式
内部类编译后的文件命名规则
内部类面试常见问题
1. 内部类的各种差异比较
2. 为什么局部内部类只能访问final局部变量?
内部类与外部类的关系
内部类与外部类的关联
总结
什么是内部类?
内部类就是定义在另一个类内部的类。写在成员位置的,属于外部类的成员。
想象一下我们的日常生活:一台电脑包含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高级开发者的必备技能。