43面向对象(高级)-内部类
2026/9/4 9:04:32 网站建设 项目流程

1.定义

一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。

内部类是类的第五大成员。(属性,方法,构造器,代码块,内部类),内部类可以访问外部类的成员(包括私有成员)

注意:内部类是学习的难点,同时也是重点,后面看底层源码时,有大量的内部类.

2.内部类的分类

1.定义在外部类局部位置上(比如方法内)

(1)局部内部类(有类名)

局部内部类(本质仍然是一个类)是定义在外部类的局部位置,通常在方法中,并且有类名。

注意:

1.可以直接访问外部类的所有成员,包含私有的

2.不能添加访问修饰符,因为它的地位就是1个局部变量。局部变量是不能使用修饰符的。但是可以使用 final 修饰,因为局部变量也可以使用final。

内部类也是一个类,所以能被其他类继承,如果不想让其他类继承,就加上final修饰。

3.作用域:仅仅在定义它的方法或代码块中。就相当于局部的变量

4.局部内部类可以直接访问外部类的成员

5.外部类在方法中,可以创建内部类的对象实例,然后即可调用内部类方法

6.外部其他类 不能访问 局部内部类(因为 局部内部类 是一个局部变量)

7.如果 外部类 和 局部内部类 的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

解读:外部类名.this 本质就是外部类的对象,即哪个对象调用了外部成员,外部类名.this 就指向哪个对象


(2)匿名内部类(没有类名,*重点*)

匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名。

说明:(1)本质是类;(2)内部类;(3)该类没有名字,系统会分配名字,自己看不到;(4)同时还是一个对象

1.基本语法
new 类或接口(参数列表){ 类体 };
2.为什么要使用匿名内部类:
//基于接口的匿名内部举例 public class AnonymousInnerClass { public static void main(string[] args){ Outer04 outer04 = new Outer04(); outer04.method(); } } public void 0uter04(){//方法 //基于接口的匿名内部类 //解读 //1.需求: 想使用IA接口,并创建对象 //2.传统方式,写一个类(tiger),实现该接口,并创建对象 IA tiger = new IA(); tiger.cry(); //3.但是被创建的类(tiger)需求是只是使用一次,后面不会再使用,如果每次都要写一类来实现接口,会显得代码很冗余 //4.可以使用匿名内部来简化开发 IA tiger = new IA(){//语法细节上:比上面传统多态方式多了{},是类的写法 @0verride public void cry(){ //相当于下面创建类重写方法 System.out.println("老虎叫唤..."); } }; tiger.cry(); //5.tiger的编译类型是? IA //6.tiger的运行类型是? 就是匿名内部类 0uter04$1。如果不是类的写法,就是多态里提到的IA /* 看底层 jdk会分配一个类名——> 0uter04$1 ,只是我们看不到 class 0uter04$1 implements IA { @0verride public void cry(){ System.out.println("老虎叫唤..."); } }*/ //7.jdk底层创建匿名内部类 0uter04$1 , 立即马上就创建了 0uter04$1 实例,并且把地址返回给 tiger //8.匿名内部类使用一次,就不能再使用。不是说tiger,tiger是对象,可以反复调用 class 0uter04{//外部类 private int n1 = 10; //属性 public void method(){ //方法 } } interface IA {//接口 public void cry(); } class Tiger implements IA { @0verride public void cry(){ System.out.println("老虎叫唤..."); } } class Dog implements IA { @0verride public void cry(){ System.out.println("小狗叫唤..."); } }
3.注意事项:

(1)匿名内部类的语法比较奇特,请大家注意,因为匿名内部类既是一个类的定义同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法有以下两种:

(2)可以直接访问外部类的所有成员,包含私有的

(3)不能添加访问修饰符,因为它的地位就是1个局部变量

(4)作用域:仅仅在定义它的方法或代码块中。就相当于局部的变量

(5)匿名内部类可以直接访问外部类的成员

(6)外部其他类 不能访问 局部内部类(因为 局部内部类 是一个局部变量)

(7)如果 外部类 和 局部内部类 的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

4.使用场景:

1.把匿名内部类 当做实参 直接传递(因为可以当做对象),简洁高效

public class InnerClassExercise01 { public static void main(string[] args){ //当做实参直接传递,f1函数形参要求传入IL接口 f1(new IL(){ @0verride public void show(){ System.out.println("这是一副名画...") } ); //传统方法,先实现接口,再创建实例传递 f1(new picture()); } public static void f1(IL il){//静态方法,形参是接口类型 il.show(); } } interfere IL(){//接口 void show(); } /*---------------------------------------------------------*/ //类实现IL ——> 编程领域(硬编码,只用一次的情况下不推荐硬编码) class picture implements IL{ @0verride public void show(){ System.out.println("这是一副名画..."); } }

2.定义在外部类的成员位置上

(1)成员内部类(没用static修饰)

成员内部类是定义在外部类的成员位置(不在方法中,地位与属性、方法是同一等级的),并且没有static修饰

注意:

1.可以直接访问外部类的所有成员,包含私有的

2.可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。

3.作用域:和外部类的其他成员一样,为整个类体。

4.成员内部类 访问 外部类成员 (比如:属性),访问方式:直接访问

5.外部类成员 访问 成员内部类 ,访问方式:创建成员内部类的对象,然后使用相关的方法

6.外部其他类 访问 成员内部类 ,访问方式:

  • 方式1

  • 方式2

7.

(2)静态内部类(使用static修饰)

静态内部类是定义在外部类的成员位置,并且有static修饰

注意:

1.可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员

2.可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员。

3.作用域:同其他的成员,为整个类体

4.静态内部类 访问 外部类成员 (比如:静态属性),访问方式:直接访问

5.外部类 访问 静态内部类 ,访问方式:创建对象,然后使用相关的方法

6.如果外部类和静态内部类的成员重名时,静态内部类访问的时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问

3.为什么需要内部类

内部类不是 Java “多此一举” 的语法,它是专门为了解决封装边界、单继承限制、代码内聚、临时实现这几类核心问题而设计的。很多 Java 核心特性(比如迭代器、集合内部结构)都依赖内部类才能优雅实现。

下面我们逐个拆解它存在的核心价值,每个点都对应一个不用内部类就很难解决的问题。


1. 最不可替代:突破封装限制,直接访问外部类私有成员

这是内部类最核心的作用。

Java 的封装规则是:private私有成员只能在本类内部访问。如果两个类逻辑强绑定、需要互相访问私有数据,不用内部类的话,就只能把属性改成public、或者加public的 get/set 方法,这会直接破坏封装。

而内部类本身就定义在外部类的 “内部”,属于外部类的一部分,因此可以无条件直接访问外部类的所有私有属性和方法,同时外部类也能访问内部类的私有成员。

最经典的实战例子就是ArrayList 的迭代器: ArrayList 存数据的数组elementDataprivate的,绝不能对外暴露。但迭代器需要遍历这个数组,于是 JDK 就把迭代器实现类Itr做成了 ArrayList 的私有内部类:

public class ArrayList<E> { private Object[] elementData; // 私有数组,对外完全不可见 private int size; // 私有内部类:迭代器实现,只有ArrayList自己能用 private class Itr implements Iterator<E> { int cursor; public E next() { // 直接访问外部类的私有数组,不需要任何getter方法 return (E) elementData[cursor++]; } } // 对外只返回Iterator接口,隐藏内部的Itr实现类 public Iterator<E> iterator() { return new Itr(); } }

这样既保证了核心数组不对外泄露,又让迭代器能直接操作数据,兼顾了封装性和运行效率。


2. 弥补短板:间接实现 “多继承”

Java 的类只能单继承(一个类只能有一个父类),这是 Java 的设计选择,但也带来了能力限制。

而内部类可以独立继承一个类,不受外部类继承关系的影响。一个外部类可以包含多个内部类,每个内部类分别继承不同的父类,这样就间接突破了单继承的限制

abstract class Animal { abstract void breathe(); } abstract class Machine { abstract void run(); } // 外部类只能继承一个父类 public class Robot extends Machine { @Override void run() { System.out.println("机器运行"); } // 内部类继承第二个父类,间接拥有两套继承体系 private class AnimalPart extends Animal { @Override void breathe() { System.out.println("模拟生物呼吸"); } } }

这种场景不用内部类的话,Java 单继承机制是完全做不到的。


3. 代码组织:强关联类归类,提升内聚性

如果一个类只服务于另一个类,其他地方完全不会用到,那把它作为内部类放进宿主类里,是最合理的代码组织方式。

好处非常直观:

  • 逻辑内聚:一看就知道这个类是专门给外部类服务的,从属关系清晰;
  • 避免包污染:不会在外层包下产生大量只给一个类用的 “工具类”,让包结构更干净。

最典型的例子就是Map.Entry: 每个键值对 Entry 是 Map 的基本组成单元,它只属于 Map,单独拿出来没有任何意义。所以 Java 把Entry定义为 Map 接口的内部接口,每个 Map 实现类(比如 HashMap)再用内部类去实现它。

4. 访问控制:更精细的隐藏实现细节

普通的顶级类,访问权限只能是public或者「包私有」(不写修饰符)。 而内部类可以用privateprotected修饰,实现更强的访问控制

  • private内部类:只有外部类自己能访问,包内其他类都看不到,完全隐藏实现细节;
  • 对外只暴露接口,实际返回内部类对象,这也是 “面向接口编程” 的经典用法。

就像上面 ArrayList 的例子,你作为使用者只能拿到Iterator接口,根本不知道有Itr这个类的存在。未来 JDK 想换迭代器的实现,直接改内部类就行,外部代码完全不受影响。


5. 开发效率:简化临时接口的实现(匿名内部类)

这是我们日常开发用得最多的场景:当你需要临时用一次某个接口 / 抽象类的实现,不想专门新建一个.java 文件的时候,用匿名内部类可以就地实现,代码非常简洁。

比如启动线程、事件监听、回调这些场景:

// 不用单独写一个类实现Runnable,直接匿名内部类就地实现 new Thread(new Runnable() { @Override public void run() { System.out.println("线程执行"); } }).start();

这本质上也是多态的一种便捷写法:接口引用指向匿名内部类的对象,和单独写实现类效果完全一样,但省去了大量冗余代码。

最后总结

内部类不是 “嵌套着玩” 的语法糖,它的每种形态都对应明确的问题场景:

  • 要访问私有成员又不破坏封装 → 成员内部类
  • 要突破单继承限制 → 成员内部类
  • 要把强关联类组织在一起 → 静态内部类 / 成员内部类
  • 要隐藏实现细节 → 私有内部类
  • 要临时实现接口 / 抽象类 → 匿名内部类

简单说:当两个类强绑定、或者需要突破普通类的访问 / 继承限制时,内部类就是最优雅的解决方案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询