Interface — hợp đồng capability với default method
Interface là gì, đa kế thừa interface, default method Java 8+, private method Java 9+, functional interface, và so sánh chi tiết với abstract class.
TL;DR: Abstract class xác định bản chất đối tượng là gì (Is-a), còn Interface quy định đối tượng có khả năng làm được gì (Can-do). Java hỗ trợ đa kế thừa interface, default/private method để mở rộng thư viện an toàn mà không phá vỡ mã nguồn cũ, cùng Functional Interface làm nền tảng cho Lambda Expression.
Ở bài trước, chúng ta đã dùng abstract class LibraryItem để dựng khung xương cốt lõi ("Is-a"): mọi ấn phẩm đều có mã số, tiêu đề và cách tính phí trễ hạn. Nhưng trong thực tế, một thực thể không chỉ có một bản chất duy nhất, mà còn sở hữu nhiều năng lực ("Can-do") độc lập: sách in mượn được, tài liệu số tải về được, bài báo lập chỉ mục tìm kiếm được.
Java cấm một class kế thừa nhiều class cha để loại trừ rủi ro tranh chấp trạng thái, nhưng cho phép class implement nhiều interface. Đây là giải pháp cho bài toán một đối tượng mang nhiều vai trò: DigitalEbook vừa là ấn phẩm thư viện, vừa có thể tải xuống (DigitalDownloadable), vừa hỗ trợ tìm kiếm toàn văn (Searchable).
1. Tại sao một class cần nhiều vai trò thay vì một cây kế thừa?
Trong đời thực, một người có chứng chỉ lái xe, chứng chỉ ngoại ngữ và chứng chỉ cứu hộ. Mỗi chứng chỉ chứng minh một năng lực hành động cụ thể. Cơ quan tuyển dụng chỉ cần biết ứng viên có chứng chỉ tương ứng hay không để giao việc, không cần ép tất cả nhân viên phải chung một nguồn gốc gia phả.

| Đời thực | Lập trình Java | Hệ thống quản lý thư viện |
|---|---|---|
| Chứng chỉ năng lực | interface | Searchable, Borrowable |
| Người có nhiều chứng chỉ | Class implement nhiều interface | PrintedBook implements Borrowable, Searchable |
| Yêu cầu trên chứng chỉ | Abstract method trong interface | List<String> searchKeywords(); |
| Hướng dẫn thực hiện mẫu | default method | default boolean matches(String query) { ... } |
Hãy tự hỏi: Thực thể này là gì (Is-a -> dùng class/abstract class) hay thực thể này làm được gì (Can-do -> dùng interface)? Nếu một khả năng có thể gắn cho nhiều đối tượng khác họ hàng (ví dụ sách in và bài giảng video đều tìm kiếm được), khả năng đó bắt buộc phải là một interface.
2. Cú pháp interface và quy ước ngầm định
Một interface năng lực tìm kiếm trong thư viện:
public interface Searchable {
// Ngam dinh: public static final int MAX_KEYWORD_LENGTH = 100;
int MAX_KEYWORD_LENGTH = 100;
// Ngam dinh: public abstract List<String> searchKeywords();
List<String> searchKeywords();
}
Các quy tắc cốt lõi:
- Mọi phương thức khai báo không có body ngầm định là
public abstract. - Mọi thuộc tính (field) trong interface ngầm định là
public static final(hằng số toàn cục). Interface không chứa instance field (không lưu trạng thái đối tượng). - Interface không có constructor và không thể khởi tạo trực tiếp bằng toán tử
new.
Lớp cụ thể hiện thực năng lực:
public class PrintedBook extends LibraryItem implements Searchable {
private final int pageCount;
private final String shelfLocation;
public PrintedBook(String id, String title, String author, int pageCount, String shelfLocation) {
super(id, title, author);
this.pageCount = pageCount;
this.shelfLocation = shelfLocation;
}
@Override
public double calculateLateFee(int daysOverdue) {
return daysOverdue * 2000.0;
}
@Override
public List<String> searchKeywords() {
return List.of(getTitle().toLowerCase(), getAuthor().toLowerCase(), shelfLocation.toLowerCase());
}
}
3. Class ghép nhiều capability qua đa kế thừa interface như thế nào?
Một tài liệu số (DigitalEbook) vừa là ấn phẩm thư viện, vừa tìm kiếm được, vừa có khả năng tải về máy:
public interface DigitalDownloadable {
String getDownloadUrl();
long getFileSizeBytes();
}
public class DigitalEbook extends LibraryItem implements Searchable, DigitalDownloadable {
private final String downloadUrl;
private final long fileSize;
public DigitalEbook(String id, String title, String author, String downloadUrl, long fileSize) {
super(id, title, author);
this.downloadUrl = downloadUrl;
this.fileSize = fileSize;
}
@Override
public double calculateLateFee(int daysOverdue) {
return 0.0; // Tai lieu so tu het han, khong tinh phi vat ly
}
@Override
public List<String> searchKeywords() {
return List.of(getTitle().toLowerCase(), getAuthor().toLowerCase(), "ebook", "pdf");
}
@Override public String getDownloadUrl() { return downloadUrl; }
@Override public long getFileSizeBytes() { return fileSize; }
}
Interface cũng có thể kế thừa interface khác qua từ khoá extends:
public interface BorrowableItem extends Searchable {
int maxBorrowDays();
boolean isAvailable();
}
4. Default method tiến hoá API mà không làm hỏng class cũ ra sao?
Trước Java 8, nếu bạn thêm một phương thức mới vào interface đã công bố (ví dụ thêm matches(String query) vào Searchable), tất cả các lớp đang implement Searchable trên toàn hệ thống lập tức bị lỗi biên dịch.
Java 8 giải quyết bằng default method:
public interface Searchable {
List<String> searchKeywords();
// Default method: Dien dat qua abstract method searchKeywords()
default boolean matches(String query) {
if (query == null || query.isBlank()) return false;
String normalized = query.trim().toLowerCase();
return searchKeywords().stream()
.anyMatch(keyword -> keyword.contains(normalized));
}
}
Các lớp cũ vẫn biên dịch bình thường và ngay lập tức dùng được hàm matches. Nếu muốn tối ưu riêng (ví dụ tra theo B-tree), lớp con có thể chủ động @Override.
JDK đã tận dụng cơ chế này để nâng cấp toàn bộ Collection Framework: Iterable.forEach(), Collection.stream(), Collection.removeIf(), Map.getOrDefault().
Xử lý xung đột Diamond Problem khi trùng default method
Khi một lớp implement hai interface có cùng tên và chữ ký default method:
public interface ArchiveRecord {
default String formatSummary() { return "Archive Record Default"; }
}
public interface CatalogEntry {
default String formatSummary() { return "Catalog Entry Default"; }
}
public class Manuscript implements ArchiveRecord, CatalogEntry {
// Bat buoc phai override de tu giai quyet xung dot
@Override
public String formatSummary() {
return ArchiveRecord.super.formatSummary() + " | " + CatalogEntry.super.formatSummary();
}
}
Trình biên dịch Java từ chối đoán mò giúp lập trình viên. Bạn bắt buộc phải ghi đè phương thức và chỉ định rõ qua cú pháp <InterfaceName>.super.<method>().
5. Static và Private method trong interface
Từ Java 8, interface cho phép phương thức static để làm hàm tiện ích hoặc Factory method:
public interface Searchable {
List<String> searchKeywords();
static boolean isQueryValid(String query) {
return query != null && !query.trim().isEmpty();
}
}
Phương thức static của interface chỉ gọi qua tên interface (ví dụ Searchable.isQueryValid("java")), không kế thừa xuống lớp con.
Từ Java 9, interface hỗ trợ phương thức private. Đây là các hàm trợ thủ gom logic dùng chung giữa nhiều default method, tránh lặp mã và không bị lộ ra ngoài:
public interface DataValidator {
default boolean validateLenient(String input) { return checkBasic(input); }
default boolean validateStrict(String input) { return checkBasic(input) && input.length() >= 5; }
private boolean checkBasic(String input) {
return input != null && !input.isBlank();
}
}
6. Functional Interface: Nền tảng của Lambda Expression
Một interface chỉ có duy nhất một abstract method được gọi là Functional Interface. Java 8 cho phép truyền trực tiếp biểu thức Lambda hoặc Method Reference vào bất kỳ nơi nào mong đợi functional interface:
@FunctionalInterface
public interface ItemFilter {
boolean test(LibraryItem item);
}
// Su dung voi Lambda expression gon gang
ItemFilter filter = item -> item.getTitle().startsWith("Java");
JDK cung cấp sẵn các functional interface chuẩn trong package java.util.function:
Predicate<T>: kiểm tra điều kiện (boolean test(T t))Function<T, R>: biến đổi dữ liệu (R apply(T t))Consumer<T>: tiêu thụ dữ liệu mà không trả về (void accept(T t))Supplier<T>: cung cấp dữ liệu mới (T get())
7. So sánh thực tế: Khi nào chọn Interface, khi nào chọn Abstract Class?
| Tiêu chí | Interface (Capability Contract) | Abstract Class (Template Base) |
|---|---|---|
| Kế thừa | Đa hiện thực (implements A, B, C) | Đơn kế thừa (extends BaseOnly) |
| Trạng thái (State) | Không có instance field, chỉ có hằng số public static final | Có đầy đủ instance field (private, protected) để lưu state |
| Constructor | Không có constructor | Có constructor để khởi tạo trạng thái của lớp cha |
| Mục đích thiết kế | Định nghĩa đối tượng làm được gì ("Can-do") | Định nghĩa bản chất danh tính đối tượng ("Is-a") |
| Lambda | Tương thích hoàn hảo nếu là Functional Interface | Không dùng được với cú pháp Lambda |
Lời khuyên thiết kế từ Effective Java (Item 20)
"Prefer interfaces to abstract classes" (Ưu tiên dùng interface thay vì abstract class).
Interface giữ thiết kế linh hoạt, không chiếm dụng khe kế thừa duy nhất và dễ dàng retrofit vào các class sẵn có. Chỉ dùng abstract class khi:
- Bạn cần chia sẻ trạng thái phức tạp (nhiều instance field gắn kết nhau).
- Bạn cần kiểm soát chặt chẽ vòng đời qua constructor của lớp cha.
- Bạn muốn áp dụng pattern Skeletal Implementation.
8. Pattern: Interface kết hợp Skeletal Abstract Class
Đây là kiến trúc kinh điển được JDK áp dụng trên Collections Framework (List + AbstractList):
- Định nghĩa Interface tạo hợp đồng linh hoạt cao nhất.
- Cung cấp một Abstract Class cài đặt sẵn phần lớn logic khung.
public interface CatalogExporter {
String generateReport(List<LibraryItem> items);
String getMimeType();
}
public abstract class AbstractCatalogExporter implements CatalogExporter {
@Override
public String generateReport(List<LibraryItem> items) {
StringBuilder sb = new StringBuilder();
sb.append(renderHeader()).append("\n");
for (LibraryItem item : items) {
sb.append(renderItem(item)).append("\n");
}
sb.append(renderFooter());
return sb.toString();
}
protected abstract String renderHeader();
protected abstract String renderItem(LibraryItem item);
protected String renderFooter() { return "--- End of Report ---"; }
}
public class PlainTextCatalogExporter extends AbstractCatalogExporter {
@Override public String getMimeType() { return "text/plain"; }
@Override protected String renderHeader() { return "=== DANH MUC AN PHAM ==="; }
@Override protected String renderItem(LibraryItem item) {
return "[" + item.getId() + "] " + item.getTitle();
}
}
Nếu một class xuất báo cáo đã trót kế thừa một base class khác, lập trình viên vẫn có thể implements CatalogExporter trực tiếp mà không bị bế tắc kế thừa.
9. Những cạm bẫy thường gặp khi thiết kế Interface
❌ Bẫy 1: Khai báo field với ý định lưu trạng thái đối tượng
public interface Config {
int TIMEOUT = 30; // Thuc chat la public static final
}
Mọi field trong interface đều là hằng số static. Đừng dùng interface để định nghĩa cấu hình mutable.
❌ Bẫy 2: Thêm abstract method vào interface đã phát hành ra bên ngoài
public interface Searchable {
List<String> searchKeywords();
double calculateRelevance(String query); // Pha vo code cua tat ca client dang dung
}
Khắc phục: Dùng default method để giữ backward compatibility.
❌ Bẫy 3: Lạm dụng Default Method để nhồi nhét nghiệp vụ phụ thuộc state Default method không thể truy cập trực tiếp biến instance. Nếu phương thức gọi lòng vòng qua hàng loạt getter/setter giả định, hãy chuyển nó sang Abstract Class hoặc Domain Service.
10. Deep Dive Oracle & OpenJDK References
- JLS §9 (Interfaces): Đặc tả kỹ thuật chính thức về quy tắc phân giải method của interface.
- JEP 335 (Default Methods): Cơ sở thiết kế thêm default method vào Java 8.
- JEP 213 (Private Interface Methods): Phương thức private trong interface từ Java 9.
- Effective Java (Joshua Bloch): Item 20: Prefer interfaces to abstract classes; Item 21: Design interfaces for posterity.
11. Tự kiểm tra kiến thức
- Q1Đoạn mã sau có biên dịch thành công không và tại sao? interface Reader { default void read() { System.out.println("Read"); } } interface Scanner { default void read() { System.out.println("Scan"); } } class MultiTool implements Reader, Scanner { }
- Q2Tại sao field trong interface luôn mặc định là public static final mà không thể là private hoặc protected?
- Q3Một interface có 1 abstract method, 2 default methods và 1 static method thì có được coi là Functional Interface không?
- Q4Pattern Skeletal Implementation mang lại lợi ích gì vượt trội so với việc chỉ dùng đơn thuần một Abstract Class?
- Q5Tại sao Effective Java Item 21 cảnh báo lập trình viên phải thận trọng khi thiết kế default method cho interface?
Bài tiếp theo: Sealed class — kiểm soát chính xác subclass + exhaustive pattern matching
Bài này có giúp bạn hiểu bản chất không?
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Abstract class — template buộc subclass hoàn thiện
Bài tiếpSealed class — kiểm soát tập subtype với pattern matching exhaustive
Lộ trình Junior Java Backend Developer
Chặng 3/21 · OOP: class, kế thừa, đa hình, abstraction — bài 13/16 của chặng
Ôn phỏng vấn
Bài này trả lời được các câu phỏng vấn sau — tự trả lời thử trước khi mở đáp án.
Thảo luận & góp ý
Hỏi đáp về bài này