×

面向对象学了这么多,你有没有想过一个问题:一个类,能不能写在另一个类的里面

我第一次听到这个说法的时候,第一反应就是——啊?类套类?这不是套娃吗?

但等你真搞懂了,会发现内部类(Inner Class)是 Java 里设计得非常精巧的一个语法。它不是炫技,而是解决了一些外部类内部逻辑高内聚的问题——说到底就是:有些类只服务于另一个类,没必要扔到外面跟人家平起平坐。

一、什么是内部类?为什么要用?

定义很简单:在某个类的内部再定义一个类,就叫内部类

class Outer {
    class Inner {
        // 内部类
    }
}

你可能会说,分开定义不就行了,何必嵌套?

场景来了:你写一个 LinkedList(双向链表)。链表由一个个节点组成,这个 Node 类只服务于 LinkedList,对外界来说 Node 就是一个实现细节,你完全没必要把它定义成一个公开的顶层类。

内部类的核心优势:

  • 封装性:外部不需要知道内部类的存在,实现细节藏得严严实实
  • 直接访问外部类成员:内部类天然持有外部类对象的引用(隐式的),可以自由访问外部类的 所有 成员,包括 private
  • 代码组织性:逻辑上属于一起的代码,物理上也放在一起

二、内部类的四种类型

Java 的内部类分四种,一个一个来:

  • 成员内部类(Member Inner Class)—— 最基础的那种
  • 静态内部类(Static Nested Class)—— 加了 static
  • 局部内部类(Local Inner Class)—— 写在方法里的
  • 匿名内部类(Anonymous Inner Class)—— 连名字都没有的

这篇我们先讲前两种——成员内部类和静态内部类。局部内部类和匿名内部类下篇再说。

三、成员内部类(Member Inner Class)

成员内部类就是最简单粗暴的:把一个类不加 static、不写方法里,就放在外部类的成员位置。

3.1 基本写法

public class Outer {

    private String name = "我是外部类";

    // 成员内部类
    public class Inner {
        public void show() {
            // 内部类直接访问外部类的 private 成员
            System.out.println(name);
        }
    }
}

调用的时候要注意,成员内部类依赖外部类对象而存在

public class Test {
    public static void main(String[] args) {
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();  // 注意这个语法
        inner.show();  // 输出:我是外部类
    }
}

outer.new Inner() 这个写法第一次见肯定觉得奇怪——记住就行,它的意思是「通过一个外部类对象来创建内部类对象」。

3.2 内部类持有外部类引用 —— Outer.this

成员内部类背后有个重要机制:每个成员内部类对象内部,都隐式持有一个外部类对象引用,通过 Outer.this 访问。

比如外部类和内部类有同名成员变量:

public class Outer {
    private String msg = "外部消息";

    public class Inner {
        private String msg = "内部消息";

        public void show() {
            String msg = "局部消息";
            System.out.println(msg);          // 局部消息
            System.out.println(this.msg);     // 内部消息
            System.out.println(Outer.this.msg); // 外部消息
        }
    }
}

Outer.this 就是那个隐式的外部类引用。三层同名变量,层层都能区分,Java 的命名空间拆得很细。

3.3 权限修饰符

成员内部类可以加 privateprotected默认public——它跟成员变量一样,访问控制全看你需要

最常见的用法就是 private

public class LinkedList {

    // 内部类私有化,外部完全不知道 Node 的存在
    private class Node {
        int data;
        Node prev;
        Node next;

        Node(int data) {
            this.data = data;
        }
    }

    private Node head;

    public void addFirst(int data) {
        Node newNode = new Node(data);
        newNode.next = head;
        if (head != null) {
            head.prev = newNode;
        }
        head = newNode;
    }
}

这就是内部类最经典的应用场景:实现细节对外界完全隐藏。调用者只需要 list.addFirst(10),根本不知道里面还有个 Node

四、静态内部类(Static Nested Class)

成员内部类加个 static 就成了静态内部类——但它俩的差别不小。

4.1 静态内部类的特点

  • 不持有外部类引用:它不依赖外部类对象,跟 static 的语义一致
  • 只能访问外部类的静态成员:不能直接访问实例成员
  • 创建不需要外部类对象new Outer.StaticInner() 搞定
public class Outer {
    private static String staticMsg = "静态消息";
    private String instanceMsg = "实例消息";

    // 静态内部类
    public static class StaticInner {
        public void print() {
            System.out.println(staticMsg);      // ✅ 可以访问外部类静态成员
            // System.out.println(instanceMsg); // ❌ 不能访问实例成员
        }
    }
}

// 使用方式
public class Test {
    public static void main(String[] args) {
        Outer.StaticInner inner = new Outer.StaticInner();
        inner.print();  // 不需要外部类实例
    }
}

4.2 经典的建造者模式

静态内部类最出名的应用就是 Builder 模式。当你有一个类需要很多可选参数时,用构造器重载能把你搞疯:

// 传统方式——参数一多构造器就爆炸
User user1 = new User("张三", 20, "北京", "男", "138xxxx", "zhangsan@qq.com");
// 哪个参数对应哪个?每次都得数位置,烦不烦

用静态内部类 + Builder 模式:

public class User {
    private final String name;      // 必填
    private final int age;          // 必填
    private final String city;      // 可选
    private final String gender;    // 可选
    private final String phone;     // 可选
    private final String email;     // 可选

    // 私有构造器——只能通过 Builder 创建
    private User(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.city = builder.city;
        this.gender = builder.gender;
        this.phone = builder.phone;
        this.email = builder.email;
    }

    // 静态内部类作为 Builder
    public static class Builder {
        private final String name;   // 必填,设为 final
        private final int age;       // 必填
        private String city;
        private String gender;
        private String phone;
        private String email;

        public Builder(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public Builder city(String city) { this.city = city; return this; }
        public Builder gender(String gender) { this.gender = gender; return this; }
        public Builder phone(String phone) { this.phone = phone; return this; }
        public Builder email(String email) { this.email = email; return this; }

        public User build() {
            return new User(this);
        }
    }
}

调用起来就清爽了:

User user = new User.Builder("张三", 20)
        .city("北京")
        .gender("男")
        .email("zhangsan@qq.com")
        .build();
// 链式调用,每一行都看得懂参数是什么意思

这是静态内部类最优雅的用法。每当你写一个构造器参数超过 3 个的类,就该考虑 Builder 模式了。

五、成员内部类 vs 静态内部类

对比维度成员内部类静态内部类
持有外部类引用✅ 持有(隐式 Outer.this❌ 不持有
访问外部实例成员✅ 可以❌ 不可以
访问外部静态成员✅ 可以✅ 可以
创建方式outer.new Inner()new Outer.Inner()
内存占用额外持有外部类引用,稍大没有额外引用,更轻量
典型场景需要双向访问(如链表 Node)辅助类(如 Builder)

六、编译后的秘密

内部类编译后会生成独立的 .class 文件。你编译 Outer.java 之后,会看到:

Outer.class
Outer$Inner.class          // 成员内部类
Outer$StaticInner.class    // 静态内部类

那个 $ 就是 Java 区分内部类的标志。如果你在命令行里看到 Outer$Inner.class 这种文件,别觉得是系统抽风——那是内部类编译后的产物。

这个规则我记得有一次写代码,反编译看字节码的时候被这个 $ 搞懵过,找了半天才发现是内部类……话说回来,搞懂这个对面试很有用,面试官经常问「内部类编译后生成几个文件」。

七、小结

  • 内部类就是类中定义的类,实现了高内聚的封装
  • 成员内部类持有隐式外部类引用,可以访问外部类全部成员
  • 静态内部类不持有外部类引用,只能访问外部类的静态成员
  • 内部类编译后生成 外部类$内部类.class 文件
  • Builder 模式是静态内部类的经典应用,参数多的类用它准没错

下一篇我们聊聊局部内部类和匿名内部类——特别是匿名内部类,你在写 GUI 事件、Lambda 表达式转译、线程创建时处处都会见到它,非常实用。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

作者

2248768396@qq.com

相关文章