做Java开发的同学肯定都见过内部类——不管是项目里的工具类、回调逻辑还是面试题里的高频考点内部类都无处不在。但很多人只停留在“会用”的层面分不清四种内部类的区别、不知道什么时候该用哪种甚至踩了很多隐藏的坑。核心总结静态内部类不依赖外部最常用、成员内部类依赖外部能访问私有、局部内部类方法内几乎不用、匿名内部类无类名简化回调最常用一、先搞懂什么是内部类为什么要用内部类在Java中内部类就是定义在另一个类外部类内部的类。它不是独立的类必须依赖外部类才能存在就像“类里的小跟班”和外部类紧密绑定。1.1 内部类的核心价值很多同学会疑惑直接写两个独立的类不好吗为什么要嵌套定义其实内部类的存在主要是为了解决3个核心问题这也是它的不可替代性•强封装性把辅助类、工具类藏在外部类内部不对外暴露避免污染外部命名空间。比如我们写一个ArrayList里面的Node节点类就适合作为内部类不用对外暴露给用户。•便捷访问内部类可以直接访问外部类的所有成员包括private修饰的私有属性和方法不用通过get/set方法简化代码。•简化代码尤其是匿名内部类能快速实现接口、抽象类的临时实例避免单独创建一个类大幅简化回调、线程、监听器等场景的代码比如Java Swing的点击事件、多线程的Runnable实现。1.2 内部类的共性注意点不管是哪种内部类有几个共性规则必须记住避免踩坑1. 内部类不能独立创建对象除了静态内部类必须依赖外部类的上下文2. 内部类编译后会生成独立的.class文件命名规则是「外部类名内部类名」比如Inner.class3. 内部类可以有自己的成员属性、方法但访问权限会受外部类和自身修饰符的限制4. 内部类和外部类是“依赖关系”不是“继承关系”内部类不会继承外部类的成员只是能直接访问。二、成员内部类普通内部类成员内部类是最基础、最常用的一种内部类仅次于静态和匿名它的核心特点是属于外部类的实例依赖外部对象才能存在。2.1 定义位置与基础语法定义在「外部类的成员位置」和外部类的成员变量、成员方法同级不加static修饰语法非常简单// 外部类 public class Outer { // 外部类私有成员属性方法 private String outerPrivateField 外部类私有属性; private void outerPrivateMethod() { System.out.println(外部类私有方法执行); } // 成员内部类普通内部类无static修饰和成员变量同级 public class Inner { // 内部类自己的成员 private int innerField 100; // 内部类的方法 public void innerMethod() { // 1. 直接访问外部类的私有属性 System.out.println(outerPrivateField); // 2. 直接访问外部类的私有方法 outerPrivateMethod(); // 3. 访问自身的成员 System.out.println(innerField); } } }2.2 核心特性成员内部类的特性全围绕“依赖外部类实例”展开记准这5点不会踩坑1.依赖外部类实例必须先创建外部类对象才能创建成员内部类对象不能独立存在2.访问权限极高能直接访问外部类的所有成员private、默认、protected、public无需任何额外操作3.外部类访问内部类外部类不能直接访问内部类的成员必须先创建内部类对象才能访问4.不能定义静态成员成员内部类里不能定义static变量、static方法、static代码块因为它属于实例不属于类5.可以有访问修饰符成员内部类可以用public、protected、默认、private修饰控制外部其他类对它的访问权限比如用private修饰就只能在外部类内部访问。2.3 成员内部类的创建方式因为依赖外部类实例所以创建成员内部类的步骤是先new外部类 → 再通过外部类对象new内部类有两种常用写法推荐第一种清晰易懂public class Test { public static void main(String[] args) { // 方式1分步创建推荐 // 1. 创建外部类实例 Outer outer new Outer(); // 2. 通过外部类实例创建成员内部类实例 Outer.Inner inner outer.new Inner(); // 3. 调用内部类方法 inner.innerMethod(); // 方式2一行简写不推荐可读性差 Outer.Inner inner2 new Outer().new Inner(); inner2.innerMethod(); } }2.4 同名成员的访问冲突如果内部类和外部类有同名的属性或方法会出现“就近访问”的问题——默认访问内部类自己的成员如何精准访问外部类的同名成员解决方案用「外部类名.this.成员名」的方式强制访问外部类的成员看示例public class Outer { String msg 外部类的msg; // 外部类同名属性 public class Inner { String msg 内部类的msg; // 内部类同名属性 public void showMsg() { System.out.println(msg); // 就近原则访问内部类的msg System.out.println(this.msg); // 明确访问内部类的msg和上面等价 System.out.println(Outer.this.msg); // 强制访问外部类的msg关键写法 } } } // 测试输出 // 内部类的msg // 内部类的msg // 外部类的msg2.5 实战场景成员内部类适合“和外部类实例紧密绑定”的场景比如• 外部类的“辅助实例”比如一个User类里面的Address地址类地址是用户的一部分依赖用户实例存在就可以作为成员内部类• 需要访问外部类私有成员的场景比如外部类有一些私有配置内部类需要用到用成员内部类可以直接访问不用暴露外部类的私有成员。三、静态内部类嵌套内部类静态内部类是四种内部类中使用频率最高的一种也是最推荐使用的一种。它的核心特点是属于外部类本身不依赖外部类实例和普通类几乎一样。重点提醒静态内部类本质就是“嵌套在外部类里的普通类”只是被外部类“包裹”起来实现封装它不依赖外部类也不持有外部类的引用。3.1 定义位置与基础语法和成员内部类一样定义在外部类的成员位置但必须加static修饰语法如下// 外部类 public class Outer { // 外部类静态成员 private static String outerStaticField 外部类静态私有属性; private static void outerStaticMethod() { System.out.println(外部类静态私有方法执行); } // 外部类非静态成员 private String outerNonStaticField 外部类非静态私有属性; // 静态内部类加static修饰和外部类静态成员同级 public static class StaticInner { // 静态内部类可以有自己的静态成员和非静态成员 private static int staticInnerField 200; private String nonStaticInnerField 静态内部类非静态属性; // 静态内部类的方法 public void innerMethod() { // 1. 直接访问外部类的静态成员不管权限 System.out.println(outerStaticField); outerStaticMethod(); // 2. 访问自身的静态和非静态成员 System.out.println(staticInnerField); System.out.println(nonStaticInnerField); // 3. 不能访问外部类的非静态成员重点 // System.out.println(outerNonStaticField); // 编译报错 } // 静态内部类的静态方法 public static void staticInnerMethod() { System.out.println(静态内部类的静态方法); } } }3.2 核心特性和成员内部类对比静态内部类和成员内部类的特性差异很大用表格对比一目了然特性成员内部类静态内部类是否依赖外部实例依赖不依赖能否访问外部静态成员能能能否访问外部非静态成员能不能能否定义静态成员不能能创建方式外部对象.new 内部类()new 外部类.内部类()3.3 静态内部类的创建方式因为不依赖外部类实例所以创建静态内部类的方式非常简单直接通过「外部类名.内部类名」创建不用先new外部类public class Test { public static void main(String[] args) { // 方式1创建静态内部类的非静态实例 Outer.StaticInner inner new Outer.StaticInner(); inner.innerMethod(); // 调用非静态方法 // 方式2直接调用静态内部类的静态方法不用创建实例 Outer.StaticInner.staticInnerMethod(); } }3.4 静态内部类不能访问外部非静态成员很多同学会疑惑为什么静态内部类不能访问外部类的非静态成员核心原因静态内部类属于外部类本身加载时不依赖外部类实例而非静态成员属于外部类实例只有创建外部类对象后才存在。一个“不依赖实例”的类无法访问“依赖实例”的成员这是Java的语法规则也是逻辑合理的你不能访问一个还没创建出来的东西。3.5 实战场景静态内部类因为不依赖外部实例、封装性好、功能独立是实际开发中最常用的内部类典型场景•工具类比如外部类是一个业务类静态内部类作为该业务的工具类封装相关的工具方法不对外暴露•数据结构的节点类比如自定义一个LinkedList里面的Node节点类就适合作为静态内部类——节点不依赖链表实例且只给链表使用•Builder模式很多框架比如OkHttp中会用静态内部类实现Builder模式用于构建外部类的实例简化对象创建•避免内存泄漏相比于成员内部类静态内部类不持有外部类的引用不会导致外部类实例无法被GC回收从而避免内存泄漏这是非常重要的一个优势。四、局部内部类局部内部类是四种内部类中使用频率最低的一种甚至很多开发多年的工程师都很少用到它。它的核心特点是定义在方法/代码块内部作用域仅限于当前方法。4.1 定义位置与基础语法定义在「外部类的方法内部」或「代码块内部」不加static修饰作用域仅限于当前方法/代码块出了方法就无法访问public class Outer { private String outerField 外部类属性; // 外部类的方法 public void outerMethod() { // 方法内的局部变量有效final int localVar 300; // Java8 可以不用写final但必须是“有效final”不被修改 // 局部内部类定义在方法内部无static修饰 class LocalInner { // 局部内部类的成员 private String innerField 局部内部类属性; // 局部内部类的方法 public void innerMethod() { // 1. 访问外部类的成员不管静态/非静态不管权限 System.out.println(outerField); // 2. 访问方法内的有效final变量 System.out.println(localVar); // 3. 访问自身成员 System.out.println(innerField); } } // 只能在当前方法内创建局部内部类的实例并使用 LocalInner inner new LocalInner(); inner.innerMethod(); } // 测试在其他方法中无法访问LocalInner public void otherMethod() { // LocalInner inner new LocalInner(); // 编译报错找不到类LocalInner } }4.2 核心特性1.作用域极窄仅在定义它的方法/代码块内部有效出了方法就无法访问包括外部类的其他方法2.依赖外部类实例和成员内部类一样依赖外部类实例不能独立创建3.访问限制能访问外部类的所有成员能访问方法内的「有效final变量」Java8 无需显式写final但变量不能被修改4.不能有访问修饰符局部内部类不能用public、private、protected修饰因为它的作用域仅限于方法内部修饰符没有意义5.不能定义静态成员和成员内部类一样不能定义static变量、static方法、static代码块。4.3 关键考点有效final变量局部内部类访问方法内的变量必须是“有效final”——什么是有效final就是变量被赋值后再也没有被修改过即使不写final关键字Java也会默认它是final。为什么要这样因为局部变量的生命周期和局部内部类的生命周期可能不一致方法执行完局部变量就会被回收但局部内部类的实例可能还存在比如被返回出去此时内部类访问的变量已经不存在了所以Java要求变量必须是final保证变量的值不会变化即使变量被回收内部类也能访问到固定的值。反例编译报错public void outerMethod() { int localVar 300; localVar 400; // 修改了局部变量不再是有效final class LocalInner { public void innerMethod() { System.out.println(localVar); // 编译报错局部变量必须是有效final } } }4.4 实战场景局部内部类的作用域太窄灵活性差实际开发中几乎不用。唯一的使用场景是方法内部需要一个临时的类且这个类只在当前方法中使用不对外暴露。比如方法内部需要实现一个接口且这个接口的实现只在当前方法中用此时可以用局部内部类但更多时候我们会用匿名内部类替代更简洁。五、匿名内部类—— 无类名、一次性简化回调的“神器”匿名内部类是四种内部类中使用频率第二高的一种核心特点是没有类名、一次性使用、直接实现接口/继承抽象类是简化代码的“神器”尤其适合回调场景。重点提醒匿名内部类本质是“没有名字的局部内部类”它的语法更简洁只能使用一次创建实例的同时完成类的定义。5.1 定义场景与基础语法匿名内部类不能单独定义必须在“创建实例的同时”定义通常用于两种场景1. 实现一个接口最常用2. 继承一个抽象类较少用。语法格式new 接口/抽象类名() { 重写方法 };没有类名直接用new关键字创建实例场景1实现接口比如我们创建一个线程用Runnable接口不用单独写一个实现类直接用匿名内部类public class Test { public static void main(String[] args) { // 匿名内部类实现Runnable接口没有类名 Runnable runnable new Runnable() { // 重写Runnable接口的run()方法 Override public void run() { System.out.println(匿名内部类实现Runnable线程执行); } }; // 启动线程 new Thread(runnable).start(); // 简写更常用直接把匿名内部类作为参数传入 new Thread(new Runnable() { Override public void run() { System.out.println(匿名内部类简写形式线程执行); } }).start(); } }场景2继承抽象类抽象类不能直接new用匿名内部类实现抽象方法快速创建实例// 抽象类 abstract class AbstractClass { public abstract void abstractMethod(); } public class Test { public static void main(String[] args) { // 匿名内部类继承抽象类重写抽象方法 AbstractClass abstractObj new AbstractClass() { Override public void abstractMethod() { System.out.println(匿名内部类继承抽象类实现抽象方法); } }; // 调用方法 abstractObj.abstractMethod(); } }5.2 核心特性1.没有类名无法重复使用只能创建一次实例一次性使用2.必须实现接口/继承抽象类不能单独定义必须依托接口或抽象类重写所有抽象方法3.依赖外部类实例和成员、局部内部类一样依赖外部类实例除非是在静态方法中定义此时依赖外部类本身4.访问限制能访问外部类的所有成员能访问方法内的有效final变量5.没有构造方法因为没有类名无法定义构造方法只能通过初始化块{}完成初始化6.可以有自己的成员可以在匿名内部类中定义自己的属性和方法但只能在内部类内部访问外部无法访问因为没有类名无法声明类型。5.3 匿名内部类的this指向匿名内部类中的this指向的是「匿名内部类自身的实例」而不是外部类的实例。如果想访问外部类的成员需要用「外部类名.this.成员名」和成员内部类一样public class Outer { private String msg 外部类msg; public void outerMethod() { // 匿名内部类 new Runnable() { private String msg 匿名内部类msg; Override public void run() { System.out.println(msg); // 匿名内部类自身的msg System.out.println(this.msg); // 匿名内部类自身的msg System.out.println(Outer.this.msg); // 外部类的msg } }.run(); } }5.4 实战场景匿名内部类的核心价值是“简化代码”适合所有“临时实现接口/抽象类、只使用一次”的场景比如•多线程实现Runnable接口、Callable接口不用单独创建实现类•回调函数比如Java Swing的点击事件、Android的按钮点击事件用匿名内部类快速实现回调逻辑•临时实现接口比如方法参数需要一个接口实例用匿名内部类直接创建不用单独定义类•简化代码替代局部内部类减少类的数量让代码更简洁Lambda表达式就是匿名内部类的简化版Java8 推荐用Lambda但匿名内部类依然有其使用场景。补充匿名内部类 vs Lambda表达式很多同学会疑惑有了Lambda表达式为什么还要用匿名内部类两者的区别的如下• Lambda表达式只能用于“函数式接口”只有一个抽象方法的接口语法更简洁• 匿名内部类可以用于“任意接口、任意抽象类”无论有多少个抽象方法灵活性更高。比如如果接口有2个抽象方法就不能用Lambda只能用匿名内部类。六、四种内部类终极对比为了方便大家记忆和对比整理了一份完整的对比表格涵盖所有核心特性面试、开发时直接翻内部类类型修饰符定义位置依赖外部实例访问外部静态成员访问外部非静态成员能否定义静态成员使用频率核心场景成员内部类无static外部类成员位置是能能不能中依赖外部实例需访问外部私有成员静态内部类有static外部类成员位置否能不能能最高工具类、节点类、Builder模式、避免内存泄漏局部内部类无static外部类方法/代码块内是能能不能极低方法内临时使用不对外暴露匿名内部类无static外部类方法/代码块内是能能不能极高回调、线程、临时实现接口/抽象类七、全文总结1. 静态内部类不依赖外部能定义静态成员最常用、最安全适合工具类、节点类2. 成员内部类依赖外部实例能访问外部所有成员不能定义静态成员适合和外部实例紧密绑定的场景3. 局部内部类方法内定义作用域极窄几乎不用了解即可4. 匿名内部类无类名、一次性简化回调和接口实现最常用不能定义构造方法。记住一句话静态不依赖成员靠外部局部藏方法匿名一次性。到这里Java四种内部类的所有知识点就全部讲完了从基础到实战从语法到避坑覆盖了开发和面试的所有重点。收藏这篇文章以后遇到内部类相关的问题直接翻就够了。最后如果你觉得这篇文章对你有帮助欢迎点赞、转发关注我每天分享Java干货带你避开更多开发坑