【java面向对象进阶】------内部类
1. 内部类简介
在 Java 面向对象编程中,内部类(Inner Class) 是一种特殊的类定义方式,它将一个类嵌套在另一个类的内部。这种设计不仅增强了代码的组织性,还体现了“整体与部分”的关系,是构建复杂系统的重要手段。
1.2 什么是内部类?
1.2.1 定义
在一个类的内部再定义一个类,这个被定义在内部的类就叫做 内部类。

1.2.2 关键点
| 特性 | 说明 |
|---|---|
| 内部类是外部类的成员 | 定义在外部类内部,与字段、方法同级,属于外部类的一部分。 |
| 内部类可直接访问外部类的所有成员 | 无论访问权限如何(包括 private),均可直接访问。 |
| 外部类访问内部类需先创建其对象 | 必须通过 new 内部类() 实例化后才能访问内部类成员。 |
| 内部类是类的五大成员之一 | 与属性、方法、构造方法、代码块并列,是类的基本组成。 |
1.3 什么时候用到内部类?
1.3.1 使用场景:
当 B 类表示的事物是 A 类的一部分,且 B 单独存在没有意义 时,应考虑使用内部类。
1.3.2 典型示例:
| 场景 | 说明 |
|---|---|
| 汽车的发动机 | 发动机是汽车的核心组成部分,脱离汽车无法独立存在; |
| ArrayList 的迭代器 | 迭代器是集合类的内部工具,用于遍历数据,不依赖外部逻辑; |
| 人的心脏 | 心脏是人体的一部分,不能脱离身体单独运行; |
1.4 代码示例
我们通过一个 “描述汽车” 的 JavaBean 案例来演示内部类的实际应用。
1.4.1 需求分析
- 定义一个
Car类,描述汽车的基本信息:- 品牌(carName)
- 车龄(carAge)
- 颜色(carColor)
- 发动机是汽车的重要组成部分,其信息包括:
- 发动机品牌(engineName)
- 使用年限(engineAge)
由于发动机是汽车的一部分,且不能脱离汽车单独存在,因此我们使用 内部类 Engine 来表示。
1.4.2 完整代码

汽车类(Car)
package com.itheima.a01class;// 外部类:Car —— 描述汽车
public class Car {String carName; // 汽车品牌int carAge; // 车龄String carColor; // 颜色// 外部类的方法public void show() {System.out.println("汽车品牌: " + carName);// 外部类访问内部类成员,需先创建内部类对象Engine engine = new Engine();System.out.println("发动机品牌: " + engine.engineName);}/*** 内部类:Engine —— 发动机* 表示汽车的组成部分,不能独立存在* 可以直接访问外部类 Car 的所有成员(包括私有成员)*/class Engine {String engineName; // 发动机品牌int engineAge; // 使用年限public void show() {// 访问内部类自身的成员System.out.println("发动机品牌: " + engineName);// 直接访问外部类 Car 的成员变量(无需任何前缀)System.out.println("所属汽车品牌: " + carName);}}
}
测试类(Test.java)
package com.itheima.a01class;public class Test {public static void main(String[] args) {/** 需求:定义一个 JavaBean 类描述汽车。** 汽车属性包括:* - 品牌(carName)* - 车龄(carAge)* - 颜色(carColor)** 发动机作为汽车的组成部分,使用内部类 Engine 表示,其属性包括:* - 品牌(engineName)* - 使用年限(engineAge)** 内部类的访问特点:* 1. 内部类可以直接访问外部类的所有成员(含私有成员)。* 2. 外部类若要访问内部类的成员,必须先创建内部类的对象。*/Car car = new Car();car.carName = "劳斯莱斯";car.carAge = 1;car.carColor = "黑色";car.show(); // 输出汽车信息和发动机品牌}
}
运行结果

2. 内部类的四大分类
| 分类 | 说明 | 是否需要掌握 |
|---|---|---|
| 成员内部类 | 定义在外部类的成员位置(与属性、方法同级),最常见的一种内部类 | 了解 |
| 静态内部类 | 使用 static 修饰的内部类,属于外部类本身,不依赖外部类实例 | 了解 |
| 局部内部类 | 定义在方法或代码块内部,作用域受限,仅在当前方法中可见 | 了解 |
| 匿名内部类 | 没有名字的内部类,通常用于实现接口或继承类,常用于事件监听等场景 | 重点 |
注意:
- 前三种内部类都有类名,可以被实例化和引用;
- 匿名内部类无类名,只能创建一次对象,常用于简化代码。
3. 成员内部类
成员内部类是 Java 内部类中最常见、最基础的一种形式。它定义在外部类的成员位置(与属性、方法同级),作为外部类的一个组成部分,能够直接访问外部类的所有成员。
3.1 什么是成员内部类?
3.1.1 定义:
成员内部类是指写在外部类的成员位置(即非方法内部)的类,属于外部类的成员之一。
3.1.2 关键特征:
- 可以使用访问修饰符(
private、default、protected、public)进行修饰; - 可以被
static修饰(此时为静态内部类,后续介绍); - 在 JDK 16 之前,不能定义静态变量;从 JDK 16 开始,允许在成员内部类中定义静态变量;
- 是外部类的“成员”,可直接访问外部类的所有成员(包括
private字段和方法)。
3.2 获取成员内部类对象的方式
由于成员内部类依赖于外部类实例存在,因此创建其对象时必须先有外部类的对象。
注意:如果成员内部类被
private修饰,则无法在外部直接访问,需通过外部类提供方法来获取。
3.2.1 两种获取方式对比
| 方式 | 说明 | 适用场景 |
|---|---|---|
| 方式一:通过外部类方法返回内部类对象 | 在外部类中编写一个方法,返回内部类实例 | 推荐!尤其当内部类为 private 时 |
| 方式二:直接使用语法创建对象 | 使用 外部类名.内部类名 对象 = new 外部类().new 内部类(); | 仅适用于非私有内部类 |
语法格式:
外部类名.内部类名 对象 = new 外部类().new 内部类();
3.3 代码示例
3.3.1 基本操作示例

完整代码
package com.itheima.a02class;// 外部类
public class Outer {// 私有成员内部类:只能在外部类内部使用private class Inner {}// 提供方法,让外部代码能获取内部类对象(因 Inner 是 private)public Inner getInstance() {return new Inner();}
}
package com.itheima.a02class;public class Test {public static void main(String[] args) {/** 成员内部类使用注意事项:* 1. 成员内部类可以使用访问修饰符(如 private、默认、protected、public)以及 static 等修饰符。* 2. 在 JDK 16 之前,成员内部类中不能定义静态变量;从 JDK 16 开始,允许定义静态变量。** 获取成员内部类对象的两种方式:* 方式一:在外部类中提供一个方法,返回内部类的实例(推荐,尤其当内部类为 private 时)。* 方式二:通过外部类实例直接创建内部类对象。* 语法格式:外部类名.内部类名 对象名 = 外部类对象.new 内部类名();* 示例:Outer.Inner oi = new Outer().new Inner();*/// 方式二(若内部类被 private 修饰,则无法在外部直接使用此方式)// Outer.Inner oi = new Outer().new Inner();// 方式一:通过外部类提供的方法获取内部类实例Outer o = new Outer();// 接收内部类对象// 法一:使用 Object 多态接收(不推荐,会丢失类型信息)Object inner = o.getInstance();// 法二:直接使用(保留具体类型,更推荐)System.out.println(o.getInstance());}
}
输出结果:
注意:
Outer$Inner是 JVM 生成的内部类名称(编译后);- 若内部类为
private,外部类必须提供方法才能访问。
3.3.2 面试常问示例
3.3.2.1 问题分析与解决
经典面试题:
当外部类成员变量、内部类成员变量、局部变量同名时,如何区分?

完整代码
package com.itheima.a03demo;public class Outer {private int a = 10; // 外部类的成员变量class Inner {private int a = 20; // 内部类的成员变量(与外部类同名)public void show() {int a = 30; // 局部变量(方法内)// 通过 Outer.this.a 访问外部类的成员变量System.out.println(Outer.this.a); // 输出:10// 通过 this.a 访问内部类自己的成员变量System.out.println(this.a); // 输出:20// 直接访问局部变量 aSystem.out.println(a); // 输出:30}}
}
package com.itheima.a03demo;public class Test {public static void main(String[] args) {// 创建成员内部类对象的语法:// 外部类名.内部类名 对象名 = new 外部类().new 内部类();Outer.Inner oi = new Outer().new Inner();// 调用内部类方法,(外部类成员、内部类成员、局部变量)oi.show();}
}
输出结果:
3.3.2.2 内存分析
一、初始状态 —— 入口类初始化前的加载阶段

程序启动时,JVM 首先加载包含
main方法的Test.class到方法区,并解析其类结构。
此时尚未执行任何对象创建操作,堆和栈均为空;
二、执行 main() 方法 —— 栈帧建立

main()方法被调用,JVM 在栈内存中为该方法创建一个栈帧,记录局部变量和操作数栈。
此时堆内存中无对象实例。
三、方法区初始化

JVM 加载
Outer.class和Test.class到方法区,并生成对应的类信息(注意这里是独立的两个字节码文件)。
此时尚未创建任何对象,栈内存为空。
四、声明引用类型变量 —— 栈中创建引用

在
main()方法中执行Outer.Inner oi;(隐含在后续赋值语句中),JVM 在栈帧中创建一个引用类型变量oi,其类型为Outer.Inner。
此时该引用尚未指向任何对象,值为null,仅在栈中预留了存储对象地址的空间。
五、创建外部类对象 new Outer()

执行
new Outer(),JVM 在堆内存中为Outer对象分配空间,并初始化其成员变量a = 10。
该对象地址(如001)存储在栈帧的局部变量表中。
六、创建内部类对象 new Outer().new Inner()

执行
new Outer().new Inner(),JVM 先获取外部类对象001,然后在堆中为Inner对象分配空间。
Inner对象会自动持有对Outer对象的引用(即this$0指向001),这是访问外部类成员的基础。
七、将内部类对象地址赋值给引用变量 oi

将内部类对象的地址
002赋值给栈中的引用变量oi,此时oi成为指向堆中Inner对象的合法引用。
八、 show() 方法进栈 —— 方法调用与栈帧创建

执行
oi.show()时,JVM 将show()方法压入栈内存,为其创建新的栈帧。
九、局部变量 int a = 30 存储于栈帧

在
show()方法中执行int a = 30;时,JVM 将该局部变量存储在当前方法的栈帧中。
此变量仅在show()方法执行期间有效,生命周期随方法结束而销毁。
它与堆内存中外部类和内部类的成员变量a(值为 10 和 20)完全独立,互不干扰。
当执行System.out.println(a);时,直接从栈帧中读取该局部变量的值30并输出。
十、this.a 的访问路径 —— 访问内部类成员变量

执行
System.out.println(this.a)时:this指向当前正在执行方法的Inner对象(地址 002);
JVM 通过this引用访问该对象自身的成员变量a; 查找堆内存中地址为 002 的内部类对象,获取其成员变量a的值(即 20);
最终输出结果:20。 该过程体现了“this”在成员内部类中用于引用自身对象的机制。
十一、Outer.this.a 的访问路径 —— 访问外部类成员变量

执行
System.out.println(Outer.this.a)时:
Outer.this表示当前内部类对象所关联的外部类对象;- JVM 通过内部类对象中隐式持有的
this$0引用(指向地址001)定位到外部类对象;- 然后访问该外部类对象的成员变量
a,其值为10;- 最终输出结果:
10。
此过程体现了成员内部类通过Outer.this间接访问外部类成员的机制。
3.4 小结
| 问题 | 回答 |
|---|---|
| 什么是成员内部类? | 写在成员位置的类,属于外部类的成员。 |
| 如何获取成员内部类对象? | 方式一:外部类提供方法返回;方式二:new 外部类().new 内部类()。 |
| 成员内部类能否定义静态变量? | JDK 16 之前不行,JDK 16 开始支持。 |
| 变量重名时如何访问? | 使用 Outer.this.变量名 访问外部类变量,this.变量名 访问内部类变量。 |
4. 静态内部类
静态内部类是 Java 内部类的一种特殊形式,它在成员内部类的基础上引入了 static 关键字。由于其特殊的访问规则和使用方式,在实际开发中被广泛用于封装工具类、配置类或与外部类逻辑紧密相关但不依赖实例的场景。
4.1 什么是静态内部类?
4.1.1 定义:
静态内部类是指使用
static修饰的成员内部类,它是外部类的静态成员之一。
4.1.2 关键特征:
- 使用
static定义; - 不依赖于外部类的实例存在,即可以在没有创建外部类对象的情况下创建静态内部类对象;
- 只能直接访问外部类的静态成员(静态变量、静态方法);
- 若要访问外部类的非静态成员,必须先创建外部类的实例;
- 编译后生成独立的
.class文件,名称为Outer$Inner.class。
4.1.3 对比普通成员内部类
| 特性 | 成员内部类 | 静态内部类 |
|---|---|---|
| 是否需要外部类实例 | 是 | 否 |
| 能否访问外部类非静态成员 | 可以(通过 Outer.this) | 不可以直接访问,需显式创建对象 |
| 是否可声明静态变量 | JDK 16 前不可,JDK 16 后可以 | 可以 |
| 创建对象语法 | new Outer().new Inner() | new Outer.Inner() |
分析:
静态内部类适合表示“属于外部类但不依赖其实例”的逻辑,例如汽车的发动机(Engine)可以作为 Car 类的静态内部类,因为发动机的功能与具体某辆车无关。
4.2 对象创建与方法调用
静态内部类的使用方式与普通成员内部类有显著区别,主要体现在对象创建和方法调用上。
4.2.1 创建静态内部类对象
Outer.Inner oi = new Outer.Inner();
无需先创建外部类对象;
直接通过外部类名.内部类名的方式创建;
因为是静态成员,所以可以像静态字段一样直接访问。
4.2.2 调用静态内部类的方法
1. 调用非静态方法
Outer.Inner oi = new Outer.Inner();
oi.show1(); // 先创建对象,再调用
2. 调用静态方法
Outer.Inner.show2(); // 直接通过类名调用,无需实例
语法小结:
- 创建对象:
外部类名.内部类名 对象名 = new 外部类名.内部类名();- 调用非静态方法:先创建对象 →
对象名.方法名()- 调用静态方法:
外部类名.内部类名.方法名()
4.3 代码示例

package com.itheima.a04demo;public class Outer {int a = 10; // 外部类的非静态成员变量static int b = 20; // 外部类的静态成员变量static class Inner {public void show1() {// 注意事项:// 静态内部类无法直接访问外部类的非静态成员(如 a),// 因此需要先创建外部类的对象,再通过该对象访问非静态成员。Outer o = new Outer();System.out.println(o.a); // 通过外部类实例访问非静态变量System.out.println(b); // 直接访问外部类的静态变量System.out.println("非静态方法被调用了");}public static void show2() {// 注意事项:// 静态方法中同样不能直接访问外部类的非静态成员,// 必须通过外部类的实例进行访问。Outer o = new Outer();System.out.println(o.a); // 通过外部类实例访问非静态变量System.out.println(b); // 直接访问外部类的静态变量System.out.println("静态方法被调用了");}}
}
package com.itheima.a04demo;public class Test {public static void main(String[] args) {// 注意事项:// 1. 静态内部类属于成员内部类的一种,使用 static 修饰。// 2. 静态内部类只能直接访问外部类的静态成员(静态变量和静态方法)。// 若要访问外部类的非静态成员,必须显式创建外部类的实例。//// 创建静态内部类对象的语法:// 外部类名.内部类名 对象名 = new 外部类名.内部类名();//// 调用静态内部类中的静态方法的语法:// 外部类名.内部类名.方法名();//// 说明:所有静态成员均可通过“类名.成员名”直接访问,无需实例化。Outer.Inner oi = new Outer.Inner();oi.show1(); // 调用静态内部类的非静态方法Outer.Inner.show2(); // 调用静态内部类的静态方法}
}
输出结果:
5. 局部内部类
局部内部类是 Java 中四种内部类(成员内部类、静态内部类、局部内部类、匿名内部类)中最少被使用但也最具“局部性”特点的一种。它定义在方法内部,作用域仅限于该方法,常用于封装仅在特定逻辑块中使用的辅助类。
5.1 什么是局部内部类?
5.1.1 定义:
局部内部类是定义在方法体内部的类,其生命周期和作用域完全局限于该方法。
5.1.2 核心特征:
- 不能使用
public、private、protected、static等访问修饰符(仅可使用final或无修饰符); - 可以访问外部类的所有成员(包括私有成员);
- 可以访问所在方法中的局部变量,但该变量必须是 “事实 final”(effectively final) —— 即在初始化后未被修改;
- 不能定义静态成员变量(但 JDK 16+ 允许定义静态方法,前提是无静态字段);
- 编译后生成独立的
.class文件,命名格式为:Outer$1Inner.class(数字表示嵌套层级)。
类比理解:
局部内部类就像方法中的“临时助手”,只在当前方法内工作,外部世界对其一无所知。
5.2 代码示例

Outer.java —— 局部内部类的定义与使用
package com.itheima.a05demo;public class Outer {int b = 20; // 外部类的非静态成员变量public void show() {int a = 10; // 方法中的局部变量(位于栈帧中)// 定义局部内部类 Innerclass Inner {String name; // 实例变量,默认值 nullint age; // 实例变量,默认值 0public void method1() {System.out.println(b); // 访问外部类成员变量System.out.println(a); // 访问方法局部变量(a 是“事实 final”)System.out.println("局部内部类中的method1方法");}// JDK 16+ 允许局部内部类定义静态方法(无静态字段时)public static void method2() {System.out.println("局部内部类中的method2方法");}}// 在方法内部创建局部内部类对象Inner i = new Inner();// 访问对象属性与方法System.out.println(i.name); // 输出 nullSystem.out.println(i.age); // 输出 0i.method1(); // 调用实例方法Inner.method2(); // 调用静态方法}
}
Test.java —— 外部调用入口
package com.itheima.a05demo;public class Test {public static void main(String[] args) {/** 局部内部类说明:* 1. 定义在方法内部,作用域仅限于该方法;* 2. 外部无法直接访问或实例化;* 3. 可访问外部类成员 + 方法中“事实 final”的局部变量。*/Outer o = new Outer();o.show(); // 触发局部内部类的定义与执行}
}
程序输出:
6.== 匿名内部类(重点)==
匿名内部类是 Java 中一种简洁而强大的语法结构,它允许我们在不定义具体类的情况下,直接创建一个实现了接口或继承了抽象类的对象。这种“即用即弃”的写法在事件处理、回调机制和简化代码中非常常见。

6.1 什么是匿名内部类?
6.1.1 定义:
匿名内部类(Anonymous Inner Class)是指没有显式类名的内部类,它通常用于一次性实现某个接口或继承某个抽象类。
6.1.2 核心特点:
- 没有类名,无法被复用;
- 只能使用一次;
- 本质是一个“隐藏名字的内部类”;
- 可以定义在成员位置,也可以定义在局部位置(如方法内);
- 实现了接口或继承了抽象类后,立即创建对象并使用。
类比:
就像你临时想做一个动作,但不想专门起个名字去定义一个新角色——直接当场完成即可。
6.2 定义格式
6.2.1 基本语法:
new 类名() { // 继承父类// 重写方法
} ;// 或者
new 接口名() { // 实现接口// 重写抽象方法
} ;
6.2.2 格式的三大要素:
| 要素 | 说明 |
|---|---|
new 类名() 或 new 接口名() | 表示继承或实现关系,创建子类/实现类对象 |
{ ... } | 匿名类的类体,可重写父类/接口的方法 |
; | 结束语句,必须加分号 |
整体含义:
创建一个没有名字的类,该类继承指定类或实现指定接口,并且立即创建其对象。
6.2.3 示例分析
new Swim() {@Overridepublic void swim() {System.out.println("重写游泳方法");}
};
这相当于:
class AnonymousSwim implements Swim {public void swim() {System.out.println("重写游泳方法");}
}
// 然后 new AnonymousSwim();
但省略了类名,直接完成实例化。
6.3 使用场景
| 场景 | 说明 |
|---|---|
| 接口实现类只使用一次 | 如 GUI 事件监听器(点击、鼠标等),无需单独定义类 |
| 简化多态传参 | 当方法参数是接口或抽象类时,可用匿名内部类直接传入实现 |
| 减少类文件数量 | 避免为简单逻辑创建大量小类,提升代码整洁度 |
| Lambda 的前身 | 在 Java 8 之前,匿名内部类是实现函数式编程的主要方式 |
建议:如果实现类只用一次,优先考虑匿名内部类;否则建议定义独立类。
6.4 代码示例

Animal.java —— 抽象类定义
package com.itheima.a06demo;// 定义抽象类 Animal
// 不能直接实例化,必须由子类继承并实现 eat() 方法
public abstract class Animal {public abstract void eat();
}
Swim.java —— 接口定义
package com.itheima.a06demo;// 定义接口 Swim
// 所有实现类必须提供 swim() 方法的具体实现
public interface Swim {public abstract void swim();
}
Dog.java —— 具体子类实现
package com.itheima.a06demo;// Dog 是 Animal 的具体实现类
// 必须重写 eat() 方法
public class Dog extends Animal {@Overridepublic void eat() {System.out.println("狗吃骨头");}
}
Test.java —— 匿名内部类的典型使用
package com.itheima.a06demo;public class Test {public static void main(String[] args) {/* * 匿名内部类语法格式:* new 类名() 或 new 接口名() {* // 重写方法* };* * 特点:无类名、只能用一次、本质是“一次性子类”*/// 1. 创建 Swim 接口的匿名实现类对象(未赋值)new Swim() {@Overridepublic void swim() {System.out.println("匿名内部类重写swim方法");}};// 2. 创建 Animal 抽象类的匿名子类对象(未赋值)new Animal() {@Overridepublic void eat() {System.out.println("匿名内部类重写eat方法");}};// 传统方式调用 method 方法// Dog d = new Dog();// method(d);// 如果只使用一次,可以用匿名内部类替代method(new Animal() {@Overridepublic void eat() {System.out.println("狗吃骨头");}});}// 接收 Animal 类型参数,体现多态public static void method(Animal a) {a.eat(); // 运行时执行匿名类中的 eat()}
}
输出结果:
说明:
method()参数类型是Animal,可以接收任何Animal的子类或匿名内部类对象;- 匿名内部类对象通过
new Animal(){...}创建,并直接传入;- 编译时看左边(
Animal),运行时看右边(匿名类的实际实现)。
Test2.java —— 两种调用方式对比
package com.itheima.a06demo;public class Test2 {public static void main(String[] args) {// 方式一:赋值给引用变量Swim s = new Swim() {@Overridepublic void swim() {System.out.println("重写游泳方法");}};s.swim(); // 通过引用调用// 方式二:链式调用(直接创建并调用)new Swim() {@Overridepublic void swim() {System.out.println("重写游泳方法");}}.swim(); // 创建后立即调用}
}
输出结果:
说明:
- 方式一适合后续多次使用;
- 方式二适用于仅调用一次的场景,代码更紧凑。
6.5 小结
| 问题 | 回答 |
|---|---|
| 什么是匿名内部类? | 隐藏了名字的内部类,可以写在成员或局部位置 |
| 格式是什么? | new 类名或接口名() { 重写方法; }; |
| 包含哪些操作? | 继承/实现 + 方法重写 + 创建对象(三合一) |
| 使用场景? | 接口或抽象类的实现类只使用一次时,简化代码 |






