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异常机制详解

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

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

相关推荐
稚辉君.MCA_P8_Java20 分钟前
DeepSeek Java 插入排序实现
java·后端·算法·架构·排序算法
程序员-周李斌22 分钟前
Java 代理模式详解
java·开发语言·系统安全·代理模式·开源软件
Cyan_RA928 分钟前
操作系统面试题 — Linux中如何查看某个端口有没有被占用?
linux·后端·面试
好学且牛逼的马28 分钟前
【Java编程思想|15-泛型】
java·windows·python
日月星辰Ace32 分钟前
JDK 工具学习系列(五):深入理解 javap、字节码与常量池
java·jvm
悟空码字37 分钟前
Spring Boot 整合 Elasticsearch 及实战应用
java·后端·elasticsearch
JienDa38 分钟前
PHP与八字命理的跨次元对话:当代码遇见命运的量子纠缠
后端
BingoGo39 分钟前
PHP 8.5 在性能、调试和运维方面的新特性
后端·php
sino爱学习40 分钟前
Guava 常用工具包完全指南
java·后端