内部类

面向对象程序设计(Java)

掌握内部类的四种形式,理解类的嵌套定义

0. 内部类概述

内部类顾名思义,就是创建在内部的类。

注意:内部类很多地方都很绕,所以说一定要仔细思考。

内部类的四种类型:

  • 成员内部类 - 作为外部类的成员存在
  • 静态内部类 - 使用static修饰的内部类
  • 局部内部类 - 定义在方法中的类
  • 匿名内部类 - 没有名字的内部类

每种内部类都有其特定的使用场景和特点。

1.1 成员内部类

学习目标

  • ✅ 理解成员内部类的定义和特点
  • ✅ 掌握成员内部类的创建和访问规则
  • ✅ 了解成员内部类与外部类的关系
  • ✅ 能够应用成员内部类解决实际问题

我们可以直接在类的内部定义成员内部类:

public class Test {
    private String outerField = "外部类的私有字段";
    
    public class Inner {   // 成员内部类:作为外部类的成员存在
        private String innerField = "内部类的字段";
        
        public void test(){
            // 可以直接访问外部类的所有成员(包括私有成员)
            System.out.println("访问外部类字段: " + outerField);
            System.out.println("访问内部类字段: " + innerField);
            System.out.println("我是成员内部类!");
        }
        
        // 内部类也可以有自己的方法
        public void innerMethod() {
            System.out.println("内部类自己的方法");
        }
    }
}

🎯 实际应用示例:GUI事件处理器

public class Button {
    private String label;
    
    public Button(String label) {
        this.label = label;
    }
    
    // 成员内部类作为点击事件处理器
    public class ClickHandler {
        public void handleClick() {
            // 可以直接访问外部类的私有成员
            System.out.println("按钮 '" + label + "' 被点击了!");
        }
    }
    
    public void addClickHandler() {
        ClickHandler handler = new ClickHandler();
        handler.handleClick();
    }
}

特点:

  • 成员内部类和成员方法、成员变量一样,是对象所有的
  • 可以使用访问权限控制(public、private等)
  • 可以访问外部类的所有成员(包括私有成员)

1.2 成员内部类的使用

如果我们要使用成员内部类,需要先创建外部类对象:

public class Test {
    private String outerField = "外部类字段";
    
    public class Inner {
        public void test(){
            System.out.println("我是成员内部类!");
            System.out.println("访问外部类字段: " + outerField);
        }
    }
    
    public static void main(String[] args) {
        // 步骤1:创建外部类对象
        Test test = new Test();   
        
        // 步骤2:通过外部类对象创建内部类对象
        // 语法:外部类对象.new 内部类构造器()
        Test.Inner inner = test.new Inner();   
        
        // 步骤3:调用内部类方法
        inner.test();   
        
        // 也可以分步创建
        Test.Inner anotherInner = test.new Inner();
        anotherInner.test();
    }
}

🔍 语法详解

类型声明: 外部类.内部类 变量名

内部类的完整类型名称包含外部类名作为前缀

对象创建: 外部类对象.new 内部类构造器()

必须通过外部类实例来创建内部类实例

依赖关系:

成员内部类对象隐含持有外部类对象的引用

语法说明:

  • 类型声明:外部类.内部类 变量名
  • 对象创建:外部类对象.new 内部类()
  • 必须先有外部类对象,才能创建内部类对象

1.3 成员内部类访问外部成员

成员内部类可以访问到外层的变量:

public class Test {
    private final String name;
    
    public Test(String name){
        this.name = name;
    }
    
    public class Inner {
        public void test(){
            System.out.println("我是成员内部类:" + name);
            // 成员内部类可以访问到外部的成员变量
            // 因为成员内部类本身就是某个对象所有的
        }
    }
}

每个外部类对象都有一个单独的内部类定义,可以通过这个成员内部类又创建出更多对象。

1.4 成员内部类的this关键字

如果内部类中也定义了同名的变量,如何明确要使用的是哪一个?

public class Test {
    private final String name;
    
    public Test(String name){
        this.name = name;
    }
    
    public class Inner {
        String name;
        public void test(String name){
            System.out.println("方法参数的name = " + name);    // 就近原则,参数
            System.out.println("成员内部类的name = " + this.name);   // 内部类对象
            System.out.println("外部类的name = " + Test.this.name); 
            // 如果需要指定为外部的对象,需要在前面添加外部类型名称
        }
    }
}

2.1 静态内部类

学习目标

  • ✅ 理解静态内部类的定义和特点
  • ✅ 掌握静态内部类的创建和访问规则
  • ✅ 区分静态内部类与成员内部类的差异
  • ✅ 了解静态内部类的适用场景

静态内部类就像静态方法和静态变量一样,是属于类的,不依赖于外部类实例:

public class Test {
    private final String name;  // 实例成员,静态内部类无法直接访问
    private static String staticField = "静态字段";  // 静态成员,静态内部类可以访问
    
    public Test(String name){
        this.name = name;
    }
    
    public static class Inner {   // 静态内部类:属于类,不依赖实例
        private String innerField = "内部类字段";
        
        public void test(){
            // 可以访问外部类的静态成员
            System.out.println("访问外部类静态字段: " + staticField);
            System.out.println("访问内部类字段: " + innerField);
            System.out.println("我是静态内部类!");
            
            // 错误:无法直接访问外部类的实例成员
            // System.out.println(name);  // 编译错误!
        }
    }
}

🎯 实际应用示例:建造者模式

建造者模式是一种创建型设计模式,用于创建复杂对象。

它将对象的创建过程分解为多个步骤,每个步骤都由一个内部类负责。

public class Computer {
    private final String cpu;
    private final String ram;
    private final String storage;
    
    // 私有构造器,只能通过Builder创建
    private Computer(String cpu, String ram, String storage) {
        this.cpu = cpu;
        this.ram = ram;
        this.storage = storage;
    }
    
    // 静态内部类作为建造者
    public static class Builder {
        private String cpu;
        private String ram;
        private String storage;
        
        public Builder cpu(String cpu) {
            this.cpu = cpu;
            return this;
        }
        
        public Builder ram(String ram) {
            this.ram = ram;
            return this;
        }
        
        public Builder storage(String storage) {
            this.storage = storage;
            return this;
        }
        
        public Computer build() {
            return new Computer(cpu, ram, storage);
        }
    }
    
    @Override
    public String toString() {
        return "Computer{cpu='" + cpu + "', ram='" + ram + "', storage='" + storage + "'}";
    }
}

// 使用示例
Computer computer = new Computer.Builder()
    .cpu("Intel i7")
    .ram("16GB")
    .storage("512GB SSD")
    .build();

特点:

  • 不需要依附任何对象,可以直接创建
  • 无法访问外部类的非静态内容
  • 相对于外部来说,整个内部类中都处于静态上下文

2.2 静态内部类的使用

不需要依附任何对象,可以直接创建静态内部类的对象:

public static void main(String[] args) {
    Test.Inner inner = new Test.Inner();   // 静态内部类可以直接new
    inner.test();
}

访问限制:

public static class Inner {
    String name;
    public void test(){
        // System.out.println(Test.this.name);  // 错误!无法访问外部非静态成员
        System.out.println("我是静态内部类:" + name);  // 可以访问自己的成员
    }
}

因为静态内部类不依附任何对象,所以无法访问外部类的非静态属性。

3.1 局部内部类

学习目标

  • ✅ 理解局部内部类的定义和特点
  • ✅ 掌握局部内部类的使用场景和限制
  • ✅ 了解局部内部类访问外部变量的规则
  • ✅ 能够应用局部内部类解决特定问题

局部内部类就像局部变量一样,定义在方法中,只在方法内部有效:

public class Test {
    private final String name;
    private static String staticField = "静态字段";
    
    public Test(String name){
        this.name = name;
    }
    
    public void test(){
        final String localVar = "局部变量";  // Java 8前必须为final
        
        class Inner {   // 局部内部类:定义在方法中
            private String innerField = "内部类字段";
            
            public void test(){
                // 可以访问外部类的所有成员
                System.out.println("访问外部类实例字段: " + name);
                System.out.println("访问外部类静态字段: " + staticField);
                
                // 可以访问方法的final局部变量(Java 8+可以访问事实上的final变量)
                System.out.println("访问方法局部变量: " + localVar);
                System.out.println("访问内部类字段: " + innerField);
                System.out.println("我是局部内部类!");
            }
        }
        
        Inner inner = new Inner();   // 在方法内部创建局部内部类对象
        inner.test();
        
        // 错误:局部内部类只能在定义它的方法内部使用
        // Inner outside = new Inner();  // 编译错误!
    }
}

🎯 实际应用示例:自定义比较器

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class StudentManager {
    private List students = new ArrayList<>();
    
    public StudentManager() {
        // 初始化学生列表
        students.add(new Student("张三", 85));
        students.add(new Student("李四", 92));
        students.add(new Student("王五", 78));
    }
    
    // 根据不同条件排序学生
    public void sortStudents(String sortBy) {
        // 局部内部类:根据成绩排序的比较器
        class ScoreComparator implements Comparator {
            @Override
            public int compare(Student s1, Student s2) {
                return Integer.compare(s1.getScore(), s2.getScore());
            }
        }
        
        // 局部内部类:根据姓名排序的比较器
        class NameComparator implements Comparator {
            @Override
            public int compare(Student s1, Student s2) {
                return s1.getName().compareTo(s2.getName());
            }
        }
        
        if ("score".equals(sortBy)) {
            Collections.sort(students, new ScoreComparator());
        } else if ("name".equals(sortBy)) {
            Collections.sort(students, new NameComparator());
        }
        
        // 打印排序结果
        for (Student student : students) {
            System.out.println(student);
        }
    }
    
    // 学生类
    private static class Student {
        private String name;
        private int score;
        
        public Student(String name, int score) {
            this.name = name;
            this.score = score;
        }
        
        public String getName() { return name; }
        public int getScore() { return score; }
        
        @Override
        public String toString() {
            return name + ": " + score;
        }
    }
}

特点:作用范围只能在方法中,使用频率很低。

4.1 匿名内部类

学习目标

  • ✅ 理解匿名内部类的定义和特点
  • ✅ 掌握匿名内部类的语法和使用方法
  • ✅ 了解匿名内部类的适用场景
  • ✅ 能够区分匿名内部类与Lambda表达式

匿名内部类是我们使用频率非常高的一种内部类,它是局部内部类的简化版,没有类名。

还记得抽象类和接口吗?它们都含有某些抽象方法需要子类去实现。

我们不能直接通过new的方式去创建一个抽象类或接口对象,但是可以使用匿名内部类。

public abstract class Student {
    private String name;
    
    public Student(String name) {
        this.name = name;
    }
    
    public abstract void study();
    
    public void introduce() {
        System.out.println("我是学生 " + name);
    }
}

正常情况下,要创建抽象类的实例对象,只能对其进行继承,先实现未实现的方法,然后创建子类对象。

4.2 匿名内部类的使用

我们可以在方法中使用匿名内部类,将其中的抽象方法实现,并直接创建实例对象:

public static void main(String[] args) {
    // 创建匿名内部类对象,实现抽象方法
    Student student = new Student("张三") {   // 在new的时候,后面加上花括号
        @Override
        public void study() {
            System.out.println("我正在学习Java内部类!");
        }
        
        // 也可以重写非抽象方法
        @Override
        public void introduce() {
            System.out.println("我是优秀学生 " + name);
        }
    };
    
    student.introduce();  // 调用继承的方法
    student.study();      // 调用实现的方法
}

此时创建出来的Student对象,就是一个已经实现了抽象方法的对象。

这个抽象类直接就定义好了,甚至连名字都没有,就可以直接创建对象。

🎯 实际应用示例:线程任务和事件处理

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class AnonymousClassExamples {
    
    // 示例1:使用匿名内部类创建线程任务
    public void runThreadTask() {
        // 匿名内部类方式:更简洁
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行匿名内部类任务");
            }
        }).start();
    }
    
    // 示例2:GUI事件处理
    public void createButton() {
        JButton button = new JButton("点击我");
        
        // 使用匿名内部类处理按钮点击事件
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("按钮被点击了!");
                JOptionPane.showMessageDialog(null, "你好,匿名内部类!");
            }
        });
    }
}

4.3 匿名内部类的特点

匿名内部类中同样可以使用类中的属性(因为它本质上就相当于是对应类型的子类):

Student student = new Student() {
    int a;   // 因为本质上就相当于是子类,所以说子类定义一些属性完全没问题
    
    @Override
    public void test() {
        System.out.println(name + "我是匿名内部类的实现!");   // 直接使用父类中的name变量
    }
};

接口的匿名内部类:

// 定义一个学习接口
interface Study {
    void study();
}

// 使用匿名内部类实现接口
Study study = new Study() {
    @Override
    public void study() {
        System.out.println("我是学习方法!");
    }
};
study.study();

4.4 匿名内部类的其他用法

并不是说只有抽象类和接口才可以创建匿名内部类,普通的类也可以:

public class NormalClass {
    public void normalMethod() {
        System.out.println("普通方法");
    }
}

public static void main(String[] args) {
    NormalClass obj = new NormalClass() {
        // 可以添加新的方法或重写现有方法
        @Override
        public void normalMethod() {
            System.out.println("重写的方法");
        }
        
        public void newMethod() {
            System.out.println("新增的方法");
        }
    };
    obj.normalMethod();
}

只不过意义不大,一般情况下只是为了进行一些额外的初始化工作而已。

5. 内部类总结

内部类类型 定义位置 创建方式 访问外部成员
成员内部类 类的成员位置 外部对象.new 内部类() 可以访问所有
静态内部类 类的成员位置 new 外部类.内部类() 只能访问静态成员
局部内部类 方法内部 new 内部类() 可以访问所有
匿名内部类 方法内部 new 父类/接口(){} 可以访问所有

使用建议:

  • 匿名内部类使用最频繁,特别是在事件处理和回调中
  • 静态内部类适合工具类的场景
  • 成员内部类和局部内部类使用相对较少

6. 练习题与自测

基础题

题目1:创建成员内部类

设计一个Car类,包含Engine成员内部类。Engine类有start()方法,输出"引擎启动"。

查看答案

题目2:静态内部类实现

使用静态内部类实现一个简单的计算器,能够进行加减乘除运算。

查看答案

进阶题

题目3:匿名内部类实现接口

定义一个Comparator接口,使用匿名内部类实现字符串长度比较器。

查看答案

* 题目4:综合应用

设计一个图书管理系统,使用不同类型的内部类实现: 1. 使用成员内部类表示图书的借阅记录 2. 使用静态内部类表示图书的分类 3. 使用匿名内部类实现图书的搜索功能

查看答案

7. 内部类使用场景对比

内部类类型 使用场景 优点 缺点 创建方式
成员内部类 需要访问外部类实例数据
GUI事件处理器
可直接访问外部类成员
封装性好
依赖外部类实例
内存占用较高
外部对象.new 内部类()
静态内部类 建造者模式
工具类辅助类
不依赖外部类实例
不依赖外部类实例
内存效率高
可包含静态成员
无法访问外部类实例成员 new 外部类.内部类()
局部内部类 方法内的特定逻辑
自定义比较器
临时辅助类
作用域限制严格
可访问方法局部变量
使用范围受限
代码可读性较差
new 内部类()(方法内)
匿名内部类 事件处理
回调函数
线程任务
一次性实现
代码简洁
减少类文件数量
适合一次性使用
不能重复使用
不能有构造器
复杂逻辑难维护
new 接口/类() {...}

选择指南

🎯 优先考虑静态内部类

如果不需要访问外部类实例成员,优先使用静态内部类,因为它更节省内存。

🎯 简单逻辑用匿名内部类

对于简单的接口实现,使用匿名内部类或Lambda表达式(Java 8+)。

推荐配套阅读:Lambda 表达式(闭包捕获与有效 final)

🎯 复杂逻辑避免匿名内部类

如果实现逻辑复杂,建议使用命名内部类或单独的类,提高代码可读性。

8. 常见错误与解决方案

❌ 错误1:静态上下文中访问非静态内部类

public class Test {
    public class Inner {}
    
    public static void main(String[] args) {
        Inner inner = new Inner(); // 编译错误!
    }
}

✅ 解决方案:

需要先创建外部类实例

public static void main(String[] args) {
    Test test = new Test();
    Test.Inner inner = test.new Inner(); // 正确
}

❌ 错误2:静态内部类访问外部类实例成员

public class Test {
    private String instanceField = "实例字段";
    
    public static class StaticInner {
        public void test() {
            System.out.println(instanceField); // 编译错误!
        }
    }
}

✅ 解决方案:

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

public class Test {
    private String instanceField = "实例字段";
    private static String staticField = "静态字段";
    
    public static class StaticInner {
        public void test() {
            System.out.println(staticField); // 正确
            // System.out.println(instanceField); // 错误
        }
    }
}

❌ 错误3:局部内部类访问非final局部变量

public void test() {
    int count = 0; // 非final变量
    class LocalInner {
        public void method() {
            count++; // 编译错误!
        }
    }
}

✅ 解决方案:

将变量声明为final或确保不被修改(Java 8+支持事实上的final)

public void test() {
    final int count = 0; // final变量
    class LocalInner {
        public void method() {
            System.out.println(count); // 正确,但不能修改
        }
    }
}

❌ 错误4:混淆内部类的this引用

public class Test {
    private String name = "外部类";
    
    public class Inner {
        private String name = "内部类";
        
        public void test() {
            System.out.println(name); // 输出"内部类"
            System.out.println(this.name); // 输出"内部类"
            // 如何访问外部类的name?
        }
    }
}

✅ 解决方案:

使用外部类名.this访问外部类成员

public void test() {
    System.out.println(name); // 输出"内部类"
    System.out.println(this.name); // 输出"内部类"
    System.out.println(Test.this.name); // 输出"外部类"
}

9. 最佳实践指南

🎯 设计原则

  • 优先使用静态内部类 - 除非需要访问外部类实例,否则使用静态内部类更节省内存
  • 保持匿名内部类简短 - 匿名内部类应保持简小,避免复杂逻辑
  • 控制局部内部类作用域 - 局部内部类应控制作用域,避免过度使用
  • 考虑Lambda表达式 - Java 8+可以使用Lambda表达式替代简单匿名内部类

🎯 性能考虑

  • 内存占用 - 成员内部类会持有外部类引用,增加内存占用
  • GC影响 - 内部类可能影响外部类的垃圾回收
  • 类加载 - 每个内部类都会生成独立的class文件

🎯 可读性建议

  • 命名规范 - 内部类应有清晰的命名,表达其用途
  • 文档注释 - 为内部类添加必要的文档注释
  • 避免过深嵌套 - 避免内部类的过度嵌套,影响代码可读性

💡 实践示例:选择合适的内部类

// 好的实践:使用静态内部类实现建造者模式
public class Product {
    private final String name;
    private final double price;
    
    private Product(String name, double price) {
        this.name = name;
        this.price = price;
    }
    
    // 静态内部类:不依赖外部类实例
    public static class Builder {
        private String name;
        private double price;
        
        public Builder name(String name) {
            this.name = name;
            return this;
        }
        
        public Builder price(double price) {
            this.price = price;
            return this;
        }
        
        public Product build() {
            return new Product(name, price);
        }
    }
}

// 好的实践:使用Lambda表达式替代简单匿名内部类(Java 8+)
button.addActionListener(e -> System.out.println("按钮被点击"));

// 避免:复杂的匿名内部类
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        // 复杂逻辑...
        // 应该提取为命名内部类或单独的类
    }
});
1 / X