CodeGym /课程 /JAVA 25 SELF /标准库中的接口:Comparable、Serializable 等

标准库中的接口:Comparable、Serializable 等

JAVA 25 SELF
第 21 级 , 课程 4
可用

1. 接口 Comparable<T>

你是否排序过数字或字符串列表?当然!现在想象你有自己定义的对象列表——例如学生、商品或猫咪。Java 怎么知道按什么顺序排序它们?这正是接口 Comparable<T> 的用武之地。

该接口定义了对象的“自然顺序”——即对该数据类型而言合理的顺序。比如数字按升序,字符串按字母序,学生可以按姓氏或年龄(由你决定)。

Comparable 的结构

这个接口很简单:它只有一个方法:

public interface Comparable<T> {
    int compareTo(T o);
}

方法 compareTo 需要返回:

  • 负数:如果当前对象“小于”另一个对象;
  • 0:如果“相等”;
  • 正数:如果“大于”。

示例:按年龄对学生排序

我们添加一个 Student 类,并为其实现 Comparable<Student>

public class Student implements Comparable<Student> {
    private String name;
    private int age;

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

    // 示例的 getter
    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public int compareTo(Student other) {
        // 按年龄升序排序
        return Integer.compare(this.age, other.age);
    }

    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

现在我们可以轻松地对学生的数组或列表进行排序:

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("Vasya", 20));
        students.add(new Student("Petya", 18));
        students.add(new Student("Masha", 22));

        Collections.sort(students); // 能工作是因为实现了 Comparable!

        System.out.println("已排序的学生:");
        for (Student s : students) {
            System.out.println(s);
        }
    }
}

结果:

Petya (18)
Vasya (20)
Masha (22)

重要细节

如果你实现了 Comparable,尽量让 compareToequals 保持一致。也就是说,如果 a.compareTo(b) == 0,那么 a.equals(b) 应为 true。否则,排序和集合可能会表现得不可预测,你也许会开始思考程序员生命的意义。

2. 接口 Serializable

序列化是指对象能够转换为字节序列(例如保存到文件或通过网络发送),随后再还原回来。想保存游戏状态或把对象发送到服务器?离不开序列化。

在 Java 中有一个标记型接口 Serializable 用于此目的。标记型意味着它不包含任何方法,只是“标记”该类可被序列化。

import java.io.Serializable;

public class Student implements Serializable {
    private String name;
    private int age;

    // 其余代码
}

如何序列化对象

用于序列化和反序列化的类是 ObjectOutputStreamObjectInputStream。示例——将对象保存到文件并读回:

import java.io.*;

public class Main {
    public static void main(String[] args) throws Exception {
        Student s = new Student("Katya", 19);

        // 将对象保存到文件
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("student.dat"))) {
            out.writeObject(s);
        }

        // 从文件读取对象
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("student.dat"))) {
            Student loaded = (Student) in.readObject();
            System.out.println("已加载: " + loaded);
        }
    }
}

注意:对象的所有字段(以及嵌套对象)也必须是可序列化的,否则会报错。

为何需要标记型接口

接口 Serializable 不要求实现任何方法——它只是告诉 JVM:“这个对象可以被序列化”。如果你忘了实现它,尝试序列化时会抛出 NotSerializableException

3. 标准库中的其他重要接口

接口 Cloneable

又一个标记型接口。它的作用是让 JVM 明白,可以通过 Object.clone() 对象进行克隆。否则调用 clone() 会抛出异常。

不过,在 Java 中克隆是个有坑的话题。默认克隆是浅拷贝(shallow copy),很多情况下更好的做法是编写自己的拷贝方法。

public class Student implements Cloneable {
    private String name;
    private int age;

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

接口 AutoCloseable

该接口只包含一个方法 close()。任何实现它的类都可以用于 try-with-resources 结构——自动关闭资源(例如文件、流):

public class MyResource implements AutoCloseable {
    @Override
    public void close() {
        System.out.println("资源已关闭!");
    }
}

public class Main {
    public static void main(String[] args) {
        try (MyResource res = new MyResource()) {
            System.out.println("正在使用资源");
        }
        // 此处将自动调用 res.close()
    }
}

接口 Iterable<T>

该接口使你的对象可以在 for-each 循环中被“遍历”。它只包含一个方法 iterator(),返回一个 Iterator<T> 对象。

public class MyList implements Iterable<String> {
    // … 内部存储

    @Override
    public java.util.Iterator<String> iterator() {
        // 返回用于遍历元素的迭代器
        return ...;
    }
}

所有标准集合(ArrayList、HashSet 等)都实现了 Iterable,因此可以在 for-each 中遍历。

接口 Comparator<T>

该接口允许在不同规则下比较对象,而无需修改对象本身。例如按姓名而不是年龄对学生排序。

import java.util.Comparator;

Comparator<Student> byName = new Comparator<Student>() {
    @Override
    public int compare(Student a, Student b) {
        return a.getName().compareTo(b.getName());
    }
};

在现代 Java 中通常通过 lambda 表达式完成:

Comparator<Student> byName = (a, b) -> a.getName().compareTo(b.getName());

Observer, EventListener

这些接口用于构建“观察者”和“事件监听器”模式——当一个对象对另一个对象中发生的事件作出反应时。例如在图形界面(Swing、JavaFX)中,按钮处理器实现 ActionListener 接口。

4. 实践:实现 Comparable 并序列化对象

示例 1. 自定义类的 Comparable

我们编写一个可以按出版年份排序的 Book 类:

public class Book implements Comparable<Book> {
    private String title;
    private int year;

    public Book(String title, int year) {
        this.title = title;
        this.year = year;
    }

    @Override
    public int compareTo(Book other) {
        return Integer.compare(this.year, other.year);
    }

    @Override
    public String toString() {
        return title + " (" + year + ")";
    }
}
import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Book> books = Arrays.asList(
            new Book("Java 入门", 2018),
            new Book("战争与和平", 1869),
            new Book("哈利·波特", 1997)
        );
        Collections.sort(books);
        System.out.println(books);
    }
}

结果:

[战争与和平 (1869), 哈利·波特 (1997), Java 入门 (2018)]

示例 2. 对象序列化

import java.io.*;

public class Book implements Serializable {
    private String title;
    private int year;
    // 构造器、getter、toString

    public Book(String title, int year) {
        this.title = title;
        this.year = year;
    }

    @Override
    public String toString() {
        return title + " (" + year + ")";
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        Book book = new Book("Java 入门", 2018);

        // 将对象保存到文件
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.dat"))) {
            out.writeObject(book);
        }

        // 从文件读取对象
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("book.dat"))) {
            Book loaded = (Book) in.readObject();
            System.out.println("已加载: " + loaded);
        }
    }
}

5. 表格:标准库中的主要接口

接口 用途 关键方法 使用示例
Comparable<T>
对象的自然顺序
int compareTo(T o)
列表排序
Comparator<T>
自定义对象比较
int compare(T a, T b)
按不同规则排序
Serializable
对象序列化 —(标记型) 对象保存/加载
Cloneable
对象克隆 —(标记型) 创建对象副本
AutoCloseable
资源自动关闭
void close()
try-with-resources
Iterable<T>
集合元素遍历
Iterator<T> iterator()
for-each 循环
Observer / EventListener 对事件作出响应
update(), actionPerformed
在 UI 中处理事件、相关模式

6. 使用标准接口时的常见错误

错误 1:未实现所需接口却使用其功能。
例如忘了实现 Serializable 却尝试序列化对象——会抛出 NotSerializableException。对 Cloneable 与调用 clone() 也同理。

错误 2:违反 Comparable 与 equals 的契约。
如果 a.compareTo(b) == 0,却满足 a.equals(b),集合可能会出现奇怪行为。例如 TreeSet 可能会“丢”对象。

错误 3:克隆时仅做浅拷贝。
clone() 默认只拷贝对象的“表层”。如果有字段是对其他对象的引用,它们不会被深拷贝。这可能导致难以察觉的 bug。

错误 4:未使用 try-with-resources。
如果类实现了 AutoCloseable,却不在 try-with-resources 中使用,你可能会忘记关闭资源——从而造成内存泄漏或文件被占用。

错误 5:错误实现 compareTo 或 compare。
如果只返回 01,而不是负/零/正数,排序将无法正确工作。

1
调查/小测验
高级接口第 21 级,课程 4
不可用
高级接口
高级接口和函数式接口
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION