CodeGym /Các khóa học /JAVA 25 SELF /Các giao diện trong thư viện chuẩn: Comparable, Serializa...

Các giao diện trong thư viện chuẩn: Comparable, Serializable, v.v.

JAVA 25 SELF
Mức độ , Bài học
Có sẵn

1. Giao diện Comparable<T>

Bạn đã từng sắp xếp danh sách số hay chuỗi chưa? Chắc chắn là rồi! Bây giờ, hãy tưởng tượng bạn có danh sách các đối tượng “của riêng mình” — ví dụ danh sách sinh viên, sản phẩm hoặc mèo. Java sẽ hiểu theo thứ tự nào để sắp xếp chúng? Đó chính là lý do cần giao diện Comparable<T>.

Giao diện này xác định “thứ tự tự nhiên” của các đối tượng — tức thứ tự hợp lý cho kiểu dữ liệu đó. Ví dụ, với số — tăng dần; với chuỗi — theo bảng chữ cái; với sinh viên — theo họ hoặc theo tuổi (tùy bạn chọn).

Comparable hoạt động thế nào

Giao diện rất đơn giản: chỉ có một phương thức:

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

Phương thức compareTo phải trả về:

  • số âm nếu đối tượng hiện tại “nhỏ hơn” đối tượng kia;
  • 0 nếu “bằng nhau”;
  • số dương nếu “lớn hơn”.

Ví dụ: sắp xếp sinh viên theo tuổi

Hãy thêm lớp Student và cài đặt cho nó giao diện 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 cho ví dụ
    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public int compareTo(Student other) {
        // Sắp xếp theo tuổi (tăng dần)
        return Integer.compare(this.age, other.age);
    }

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

Bây giờ ta có thể dễ dàng sắp xếp mảng hay danh sách sinh viên:

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); // Hoạt động nhờ Comparable!

        System.out.println("Danh sách sinh viên đã được sắp xếp:");
        for (Student s : students) {
            System.out.println(s);
        }
    }
}

Kết quả:

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

Lưu ý quan trọng

Nếu bạn cài đặt Comparable, hãy cố gắng để compareTo vận hành nhất quán với equals. Tức là nếu a.compareTo(b) == 0, thì a.equals(b) phải true. Nếu không, sắp xếp và các collection có thể cư xử khó lường, và bạn sẽ có cớ để suy tư về ý nghĩa cuộc đời của lập trình viên.

2. Giao diện Serializable

Tuần tự hóa (serialization) — là khả năng một đối tượng biến thành dãy byte (ví dụ để tự lưu vào tệp hoặc gửi qua mạng), rồi khôi phục lại. Hãy tưởng tượng bạn muốn lưu trạng thái trò chơi hoặc gửi đối tượng lên server — thiếu tuần tự hóa là không được.

Trong Java có giao diện đánh dấu (marker) Serializable. “Đánh dấu” — nghĩa là nó không chứa phương thức, mà chỉ “đánh dấu” lớp là có thể tuần tự hóa.

import java.io.Serializable;

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

    // ... phần mã còn lại
}

Cách tuần tự hóa một đối tượng

Để tuần tự hóa và giải tuần tự sử dụng các lớp ObjectOutputStreamObjectInputStream. Ví dụ — lưu đối tượng vào tệp và đọc lại:

import java.io.*;

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

        // Lưu đối tượng vào tệp
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("student.dat"))) {
            out.writeObject(s);
        }

        // Đọc đối tượng từ tệp
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("student.dat"))) {
            Student loaded = (Student) in.readObject();
            System.out.println("Đã tải: " + loaded);
        }
    }
}

Lưu ý: Tất cả các trường của đối tượng (và các đối tượng lồng bên trong) cũng phải có thể tuần tự hóa, nếu không sẽ xảy ra lỗi.

Tại sao cần marker interface

Giao diện Serializable không yêu cầu cài đặt phương thức nào — nó chỉ thông báo cho JVM: “đối tượng này có thể tuần tự hóa”. Nếu quên không cài đặt, nỗ lực tuần tự hóa sẽ dẫn đến ngoại lệ NotSerializableException.

3. Các giao diện quan trọng khác của thư viện chuẩn

Giao diện Cloneable

Lại là một marker interface. Nhiệm vụ của nó — cho JVM biết rằng đối tượng có thể nhân bản (clone) bằng phương thức Object.clone(). Nếu không có nó, gọi clone() sẽ ném ngoại lệ.

Tuy nhiên, cloning trong Java — là chủ đề có “cạm bẫy”. Việc clone mặc định là nông (shallow copy), và thường thay vì thế bạn nên viết các phương thức sao chép của riêng mình.

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

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

Giao diện AutoCloseable

Giao diện này chỉ có một phương thức close(). Bất kỳ lớp nào cài đặt nó đều có thể dùng trong cấu trúc try-with-resources — tự động đóng tài nguyên (ví dụ tệp, stream):

public class MyResource implements AutoCloseable {
    @Override
    public void close() {
        System.out.println("Tài nguyên đã được đóng!");
    }
}

public class Main {
    public static void main(String[] args) {
        try (MyResource res = new MyResource()) {
            System.out.println("Đang làm việc với tài nguyên");
        }
        // Ở đây res.close() sẽ được gọi tự động
    }
}

Giao diện Iterable<T>

Giao diện này cho phép đối tượng của bạn được “duyệt” trong vòng lặp for-each. Chỉ có một phương thức iterator(), trả về đối tượng Iterator<T>.

public class MyList implements Iterable<String> {
    // ... kho lưu trữ nội bộ

    @Override
    public java.util.Iterator<String> iterator() {
        // Trả về iterator để duyệt các phần tử
        return ...;
    }
}

Tất cả các collection chuẩn (ArrayList, HashSet, v.v.) đều cài đặt Iterable, vì vậy có thể duyệt bằng for-each.

Giao diện Comparator<T>

Giao diện này cho phép so sánh các đối tượng theo nhiều quy tắc khác nhau mà không cần sửa chính đối tượng. Ví dụ, sắp xếp sinh viên theo tên thay vì theo tuổi.

import java.util.Comparator;

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

Trong Java hiện đại, việc này thường làm qua lambda:

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

Observer, EventListener

Các giao diện này dùng để xây dựng các pattern “observer” và “event listener” — khi một đối tượng phản ứng với sự kiện xảy ra ở đối tượng khác. Ví dụ, trong giao diện đồ họa (Swing, JavaFX) các handler của nút bấm cài đặt giao diện ActionListener.

4. Thực hành: cài đặt Comparable và tuần tự hóa đối tượng

Ví dụ 1. Comparable cho lớp tự định nghĩa

Hãy viết lớp Book, có thể sắp xếp theo năm xuất bản:

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 dlya chaynikov", 2018),
            new Book("Voyna i mir", 1869),
            new Book("Garri Potter", 1997)
        );
        Collections.sort(books);
        System.out.println(books);
    }
}

Kết quả:

[Voyna i mir (1869), Garri Potter (1997), Java dlya chaynikov (2018)]

Ví dụ 2. Tuần tự hóa đối tượng

import java.io.*;

public class Book implements Serializable {
    private String title;
    private int year;
    // ... constructor, 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 dlya chaynikov", 2018);

        // Lưu đối tượng vào tệp
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.dat"))) {
            out.writeObject(book);
        }

        // Đọc đối tượng từ tệp
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("book.dat"))) {
            Book loaded = (Book) in.readObject();
            System.out.println("Đã tải: " + loaded);
        }
    }
}

5. Bảng: các giao diện chính của thư viện chuẩn

Giao diện Mục đích Phương thức chính Ví dụ sử dụng
Comparable<T>
Thứ tự tự nhiên của đối tượng
int compareTo(T o)
Sắp xếp danh sách
Comparator<T>
So sánh đối tượng theo quy tắc tùy chỉnh
int compare(T a, T b)
Sắp xếp theo nhiều tiêu chí khác nhau
Serializable
Tuần tự hóa đối tượng — (marker) Lưu/tải đối tượng
Cloneable
Nhân bản (clone) đối tượng — (marker) Tạo bản sao đối tượng
AutoCloseable
Tự động đóng tài nguyên
void close()
try-with-resources
Iterable<T>
Duyệt phần tử trong collection
Iterator<T> iterator()
Vòng lặp for-each
Observer / EventListener Phản ứng với sự kiện
update(), actionPerformed
Xử lý sự kiện trong UI, các pattern

6. Các lỗi thường gặp khi làm việc với các giao diện chuẩn

Lỗi 1: Chưa cài đặt giao diện nhưng lại cần chức năng.
Ví dụ, quên cài đặt Serializable mà lại cố tuần tự hóa đối tượng — sẽ nhận NotSerializableException. Tương tự với Cloneable và việc gọi clone().

Lỗi 2: Vi phạm hợp đồng giữa Comparable và equals.
Nếu a.compareTo(b) == 0 nhưng không thỏa a.equals(b), các collection có thể cư xử kỳ lạ. Ví dụ, TreeSet có thể “mất” đối tượng.

Lỗi 3: Sao chép nông khi clone.
Phương thức clone() mặc định chỉ sao chép “lớp bên trên” của đối tượng. Nếu bạn có các trường tham chiếu đến đối tượng khác, chúng sẽ không được sao chép sâu. Điều này có thể dẫn đến những bug khó hiểu.

Lỗi 4: Không dùng try-with-resources.
Nếu lớp cài đặt AutoCloseable nhưng bạn không dùng trong try-with-resources, bạn có nguy cơ quên đóng tài nguyên — và gặp rò rỉ bộ nhớ hoặc khóa tệp.

Lỗi 5: Cài đặt sai compareTo hoặc compare.
Nếu chỉ trả về 0 hoặc 1, thay vì số âm/0/dương, việc sắp xếp sẽ không chính xác.

1
Khảo sát/đố vui
, cấp độ , bài học
Không có sẵn
Các interface nâng cao
Các interface nâng cao và functional interface
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION