内部类
面向对象程序设计(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()方法,输出"引擎启动"。
public class Car {
private String brand;
public Car(String brand) {
this.brand = brand;
}
// 成员内部类
public class Engine {
public void start() {
System.out.println(brand + "的引擎启动");
}
}
public static void main(String[] args) {
Car car = new Car("宝马");
Car.Engine engine = car.new Engine();
engine.start(); // 输出:宝马的引擎启动
}
}
题目2:静态内部类实现
使用静态内部类实现一个简单的计算器,能够进行加减乘除运算。
public class Calculator {
// 静态内部类
public static class Operation {
private double a;
private double b;
public Operation(double a, double b) {
this.a = a;
this.b = b;
}
public double add() { return a + b; }
public double subtract() { return a - b; }
public double multiply() { return a * b; }
public double divide() {
if (b != 0) return a / b;
throw new ArithmeticException("除数不能为0");
}
}
public static void main(String[] args) {
Calculator.Operation op = new Calculator.Operation(10, 5);
System.out.println("加法: " + op.add()); // 15.0
System.out.println("减法: " + op.subtract()); // 5.0
System.out.println("乘法: " + op.multiply()); // 50.0
System.out.println("除法: " + op.divide()); // 2.0
}
}
进阶题
题目3:匿名内部类实现接口
定义一个Comparator接口,使用匿名内部类实现字符串长度比较器。
import java.util.Arrays;
import java.util.Comparator;
public class StringComparator {
public static void main(String[] args) {
String[] words = {"Java", "Python", "C", "JavaScript"};
// 使用匿名内部类创建比较器
Comparator lengthComparator = new Comparator() {
@Override
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
};
Arrays.sort(words, lengthComparator);
System.out.println(Arrays.toString(words));
// 输出: [C, Java, Python, JavaScript]
}
}
* 题目4:综合应用
设计一个图书管理系统,使用不同类型的内部类实现: 1. 使用成员内部类表示图书的借阅记录 2. 使用静态内部类表示图书的分类 3. 使用匿名内部类实现图书的搜索功能
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class LibrarySystem {
private String name;
private List books = new ArrayList<>();
public LibrarySystem(String name) {
this.name = name;
}
// 成员内部类:借阅记录
public class BorrowRecord {
private Book book;
private String borrower;
private String borrowDate;
public BorrowRecord(Book book, String borrower, String borrowDate) {
this.book = book;
this.borrower = borrower;
this.borrowDate = borrowDate;
}
public void displayRecord() {
System.out.println("借阅记录: " + book.getTitle() +
" 借阅人: " + borrower +
" 日期: " + borrowDate);
}
}
// 静态内部类:图书分类
public static class Category {
private String name;
private String code;
public Category(String name, String code) {
this.name = name;
this.code = code;
}
public String getName() { return name; }
public String getCode() { return code; }
}
// 图书类
private static class Book {
private String title;
private String author;
private Category category;
public Book(String title, String author, Category category) {
this.title = title;
this.author = author;
this.category = category;
}
public String getTitle() { return title; }
public String getAuthor() { return author; }
public Category getCategory() { return category; }
}
// 使用匿名内部类实现搜索功能
public List searchBooks(String keyword) {
Predicate searchPredicate = new Predicate() {
@Override
public boolean test(Book book) {
return book.getTitle().contains(keyword) ||
book.getAuthor().contains(keyword);
}
};
List result = new ArrayList<>();
for (Book book : books) {
if (searchPredicate.test(book)) {
result.add(book);
}
}
return result;
}
public void addBook(String title, String author, Category category) {
books.add(new Book(title, author, category));
}
}
7. 内部类使用场景对比
| 内部类类型 | 使用场景 | 优点 | 缺点 | 创建方式 |
|---|---|---|---|---|
| 成员内部类 | 需要访问外部类实例数据 GUI事件处理器 |
可直接访问外部类成员 封装性好 |
依赖外部类实例 内存占用较高 |
外部对象.new 内部类() |
| 静态内部类 | 建造者模式 工具类辅助类 不依赖外部类实例 |
不依赖外部类实例 内存效率高 可包含静态成员 |
无法访问外部类实例成员 | new 外部类.内部类() |
| 局部内部类 | 方法内的特定逻辑 自定义比较器 临时辅助类 |
作用域限制严格 可访问方法局部变量 |
使用范围受限 代码可读性较差 |
new 内部类()(方法内) |
| 匿名内部类 | 事件处理 回调函数 线程任务 一次性实现 |
代码简洁 减少类文件数量 适合一次性使用 |
不能重复使用 不能有构造器 复杂逻辑难维护 |
new 接口/类() {...} |
选择指南
🎯 优先考虑静态内部类
如果不需要访问外部类实例成员,优先使用静态内部类,因为它更节省内存。
🎯 复杂逻辑避免匿名内部类
如果实现逻辑复杂,建议使用命名内部类或单独的类,提高代码可读性。
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) {
// 复杂逻辑...
// 应该提取为命名内部类或单独的类
}
});