Day15 | Java内部类详解

一、什么是内部类

内部类是定义在另一个类内部的类,它可以直接访问外部类的所有成员(包括私有成员),是实现封装、回调和高内聚设计的核心语法。

Java里面不仅可以在文件里定义类,还可以在类里嵌套类,这种就是内部类。

这种设计主要是为了解决一些实际问题,比如:

某个类只为另一个类服务时,不暴露它的存在;

表达逻辑更紧耦的协作关系;

实现更优雅的回调、观察者、迭代器等模式;

代替多继承带来的复杂性。

java 复制代码
package com.lazy.snail.day15;

/**
 * @ClassName Outer
 * @Description TODO
 * @Author lazysnail
 * @Date 2025/5/29 9:21
 * @Version 1.0
 */
public class Outer {
    private String name = "我是Outer";

    public class Inner {
        public void showName() {
            System.out.println(name);
        }
    }
}

上述案例中,Outer是定义的一个普通类,Inner是在Outer类中(类定义大括号内)定义的一个内部类。

编译项目(IDEA中使用快捷键Ctrl + F9),然后找到对应字节码文件(.class)。

项目字节码文件输出路径一般在项目中有个默认路径,当然也可以自定义。

使用快捷键Ctrl + Shift + Alt + S打开项目结构-->Compiler output设置的路径就是编译后文件所在路径:

然后根据包名,类名,找到对应的字节码文件。

可以看到两个字节码文件,Outer.class和Outer$Inner.class。

这种命名方式是编译器决定的。

注意下面这种形式并不是内部类:

java 复制代码
package com.lazy.snail.day15;

/**
 * @ClassName Outer
 * @Description TODO
 * @Author lazysnail
 * @Date 2025/5/29 9:21
 * @Version 1.0
 */
public class Outer {
    private String name = "我是Outer";
}

class Inner {
    
}

编译后的结果:

这种形式只是在同一个源文件中定义了两个类,不存在嵌套的关系,相互独立。

二、四种内部类

2.1 成员内部类

第一章的案例其实就是成员内部类。

java 复制代码
package com.lazy.snail.day15;

/**
 * @ClassName Outer
 * @Description TODO
 * @Author lazysnail
 * @Date 2025/5/29 9:21
 * @Version 1.0
 */
public class Outer {
    private String name = "我是Outer";

    public class Inner {
        public void showName() {
            System.out.println(name);
        }
    }

    public static void main(String[] args) {
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.showName();
    }
}

Inner和Outer存在绑定关系,Inner可以访问外部类的所有成员,包括私有字段。

内部类编译后的文件名Outer$Inner.class。

成员内部类持有外部类的引用,如果生命周期不一致,可能导致外部类无法被GC。

2.2 静态内部类

静态内部类不依赖外部类实例。

只能访问外部类的静态成员。

在集合框架中常见。

上图是HashMap的源代码,Node类就是HashMap中定义的一个静态内部类。

2.3 局部内部类

局部内部类定义在方法里,作用域仅限方法内部。

可以访问方法里的final或effectively final变量。

用于封装临时的小逻辑块。

java 复制代码
package com.lazy.snail.day15;

/**
 * @ClassName Outer2
 * @Description TODO
 * @Author lazysnail
 * @Date 2025/5/29 10:12
 * @Version 1.0
 */
public class Outer2 {
    public void great(String name) {
        class Greater {
            public void sayHi() {
                System.out.println("你好 " + name);
            }
        }
        new Greater().sayHi();
    }

    public static void main(String[] args) {
        Outer2 outer2 = new Outer2();
        outer2.great("懒惰蜗牛");
    }
}

Java 8+,方法参数/局部变量不需要显式加final,只要没被修改,编译器会隐式处理成final(effectively final)

2.4 匿名内部类

匿名内部类就是没有名字的内部类。

主要用于快速实现接口或抽象类。

在事件监听、线程创建等场景中常见。

匿名内部类不能有构造方法,也不能实现多个接口。

如果你写过线程相关的代码,你应该见过这段代码:

java 复制代码
package com.lazy.snail.day15;

/**
 * @ClassName Outer3
 * @Description TODO
 * @Author lazysnail
 * @Date 2025/5/29 10:21
 * @Version 1.0
 */
public class Outer3 {
    public static void main(String[] args) {
        Runnable task = new Runnable() {
            @Override
            public void run() {
                System.out.println("跑线程任务");
            }
        };
        new Thread(task).start();
    }
}

其实这段代码中task的创建等价于:

java 复制代码
class AnonymousRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("跑线程任务");
    }
}
Runnable task = new AnonymousRunnable();

上下一对比,可以看出,匿名内部类就是适用于快速创建线程任务且实现简单的场景。

单纯的创建一个简单的线程任务,我们没必要再去写个具体的类来实现Runnable接口,再创建对象。

在编译后的字节码文件中,会出现Outer3$1.class文件,该文件反编译后:

其实就是等价代码的逻辑。

Java 8+引入Lambda表达式后,代码可以进一步简化成:

java 复制代码
package com.lazy.snail.day15;

/**
 * @ClassName Outer3
 * @Description TODO
 * @Author lazysnail
 * @Date 2025/5/29 10:21
 * @Version 1.0
 */
public class Outer3 {
    public static void main(String[] args) {
        Runnable task = () -> System.out.println("跑线程任务");
        new Thread(task).start();
    }
}

Lambda表达式的写法不会再出现Outer3$1.class这样的字节码。

匿名内部类的简单演进过程:

下面是Lambda表达式和匿名内部类的差异总结:

特性 Lambda表达式 匿名内部类
本质 函数式接口的简洁实现 马上就创建类实例
语法结构 (params) -> expression new Interface() { 方法实现 }
编译机制 使用invokedynamic指令动态生成 生成独立.class文件(比如Outer3$1.class)
作用域 共享外围作用域 有独立作用域
this关键字 指向外围类实例 指向自身实例
变量捕获 可以捕获effectively final变量(隐式final) 需显式final变量
内存占用 较小(JVM级优化) 较大(生成新类)
方法支持 只能实现单个抽象方法 可实现多方法
构造函数 不支持 支持实例初始化块
接口类型 仅限函数式接口(单一抽象方法) 支持任意接口/抽象类
调试信息 栈轨迹更简洁 有完整类名(但匿名类名可读性差)
字节码验证 运行时校验 编译时校验

结语

内部类这种设计让类的定义不局限于文件或包,可以灵活嵌套,配合作用域、访问控制、编译策略,一起构建高内聚、低耦合的结构。

在真实项目开发里,要不要使用内部类,关键在于能不能提升代码的可读性与模块性。

不是所有内部类都值得保留,也不是所有逻辑都该外提成独立类。

如果你把内部类当成语法糖,那它就是个工具;

但如果你把它当作结构表达力的一部分,那它就是一种架构语言。

下一篇预告

Day16 | Java异常机制详解

如果你觉得这系列文章对你有帮助,欢迎关注专栏,我们一起坚持下去!

更多文章请关注我的公众号《懒惰蜗牛工坊》

相关推荐
海兰19 分钟前
使用 Spring AI 打造企业级 RAG 知识库第二部分:AI 实战
java·人工智能·spring
历程里程碑36 分钟前
二叉树---二叉树的中序遍历
java·大数据·开发语言·elasticsearch·链表·搜索引擎·lua
小信丶1 小时前
Spring Cloud Stream EnableBinding注解详解:定义、应用场景与示例代码
java·spring boot·后端·spring
无限进步_1 小时前
【C++】验证回文字符串:高效算法详解与优化
java·开发语言·c++·git·算法·github·visual studio
亚历克斯神1 小时前
Spring Cloud 2026 架构演进
java·spring·微服务
七夜zippoe1 小时前
Spring Cloud与Dubbo架构哲学对决
java·spring cloud·架构·dubbo·配置中心
海派程序猿1 小时前
Spring Cloud Config拉取配置过慢导致服务启动延迟的优化技巧
java
阿维的博客日记1 小时前
为什么不逃逸代表不需要锁,JIT会直接删掉锁
java
William Dawson1 小时前
CAS的底层实现
java
ffqws_1 小时前
Spring Boot入门:通过简单的注册功能串联Controller,Service,Mapper。(含有数据库建立,连接,及一些关键注解的讲解)
数据库·spring boot·后端