1. Abstract class: ôn lại cơ bản
Trước khi đi vào so sánh, hãy nhắc lại abstract class là gì.
Abstract class — là một class không thể tạo instance trực tiếp (không thể viết new Animal()), nhưng có thể chứa cả phương thức thông thường (đã có triển khai) lẫn phương thức abstract (chưa có triển khai). Abstract class thường được dùng làm nền tảng cho các class khác kế thừa hành vi của nó và/hoặc buộc phải triển khai một số phương thức.
Ví dụ, nếu trong ứng dụng của chúng ta có nhiều loại phương tiện, ta có thể tạo một abstract class Transport:
public abstract class Transport {
private String model;
public Transport(String model) {
this.model = model;
}
public String getModel() {
return model;
}
// Phương thức abstract — không có triển khai, chỉ khai báo
public abstract void move();
// Phương thức thông thường — có triển khai
public void printInfo() {
System.out.println("Mẫu phương tiện: " + model);
}
}
Đặc điểm của abstract class:
- Có thể chứa field (trạng thái).
- Có thể chứa các phương thức đã được triển khai.
- Có thể chứa các phương thức abstract (bắt buộc các lớp con phải triển khai).
- Không thể tạo instance trực tiếp.
- Dùng để chia sẻ hành vi và trạng thái chung.
2. Interface: ôn lại cơ bản
Interface — là tập hợp các phương thức mà một class phải triển khai. Interface không mô tả trạng thái (không có field thông thường, chỉ có hằng số), và không chứa phần triển khai phương thức (trước Java 8). Interface là một bản hợp đồng thuần túy: “Nếu bạn triển khai tôi, bạn bắt buộc phải làm được những việc này”.
Ví dụ về interface:
public interface Movable {
void move(int x, int y);
}
Đặc điểm của interface:
- Không chứa trạng thái (chỉ có public static final hằng số).
- Trước Java 8 — chỉ có các phương thức abstract (từ Java 8 xuất hiện phương thức default và static, sẽ nói sau).
- Mặc định, các phương thức luôn là public abstract.
- Một class có thể triển khai nhiều interface.
- Dùng để mô tả khả năng — “class có thể làm gì”.
3. Bảng so sánh: abstract class vs interface
Đã đến lúc so sánh trực diện hai công cụ này! Dưới đây là bảng trực quan:
| Đặc điểm | Abstract class | Interface |
|---|---|---|
| Cú pháp | |
|
| Có thể tạo instance? | Không | Không |
| Có thể có phương thức thông thường? | Có | Trước Java 8 — không; từ Java 8 — chỉ default/static |
| Có thể có phương thức abstract? | Có | Có (trước Java 8, tất cả các phương thức đều là abstract) |
| Có thể có field (trạng thái)? | Có (bất kỳ field nào) | Chỉ public static final (hằng số) |
| Có thể có constructor? | Có | Không |
| Kế thừa | Chỉ một class (abstract hoặc thường) | Có thể triển khai nhiều interface |
| Modifier của phương thức | Bất kỳ (public, protected, private) | Mặc định phương thức là public abstract. Từ Java 9 có thể thêm phương thức private để dùng bên trong interface |
| Kế thừa/triển khai qua | |
|
| Thường dùng để làm gì | Chung phần triển khai và trạng thái | Mô tả khả năng, vai trò |
| Ví dụ trong JDK | |
|
4. Khi nào dùng interface, khi nào dùng abstract class?
Interface dùng khi:
- Bạn muốn mô tả “class làm được gì” mà không quan tâm nó làm thế nào.
- Bạn cần class có thể triển khai nhiều khả năng độc lập.
- Ví dụ: Comparable (có thể so sánh), Serializable (có thể tuần tự hóa), Runnable (có thể chạy trong thread).
Abstract class dùng khi:
- Bạn muốn đặt ra phần triển khai và trạng thái chung cho mọi lớp con.
- Bạn cần tất cả lớp con có những field nhất định hoặc các phương thức đã được triển khai.
- Kế thừa — nghiêm ngặt “một-một”: class chỉ có thể kế thừa một class (thường hoặc abstract).
Ẩn dụ đời sống
- Interface — giống như “giấy phép lái xe”: nếu bạn có nó, bạn có thể lái xe, nhưng không ai nói bạn lái chiếc nào và cách bạn lái ra sao.
- Abstract class — giống như “bản thiết kế chung của ô tô”: mọi xe đều có vô lăng, bàn đạp, động cơ, nhưng mỗi hãng sẽ hiện thực chi tiết theo cách riêng.
5. Ví dụ từ thư viện chuẩn Java
Interface: Comparable
public interface Comparable<T> {
int compareTo(T o);
}
Bất kỳ class nào triển khai interface này đều phải triển khai phương thức compareTo. Ví dụ: String, Integer, LocalDate và nhiều lớp khác.
Abstract class: AbstractList
public abstract class AbstractList<E> implements List<E> {
// Triển khai mặc định một số phương thức của List
// Một số phương thức được để ở dạng abstract
}
AbstractList đã triển khai sẵn một phần hành vi của các collection (ví dụ, các phương thức thêm/xóa), nhưng để lại một số phương thức ở dạng abstract để các lớp con có thể hiện thực theo cách của mình.
6. Ví dụ mã: so sánh trong thực tế
Interface
Tạo một interface và một class triển khai nó.
public interface Printable {
void print();
}
public class Document implements Printable {
@Override
public void print() {
System.out.println("Đang in tài liệu...");
}
}
Abstract class
Bây giờ là abstract class và lớp con của nó.
public abstract class Machine {
public void turnOn() {
System.out.println("Máy đã bật.");
}
public abstract void work();
}
public class Printer extends Machine {
@Override
public void work() {
System.out.println("Máy in đang in...");
}
}
Một class vừa kế thừa abstract class vừa triển khai interface
public class SmartPrinter extends Machine implements Printable {
@Override
public void work() {
System.out.println("Máy in thông minh đang hoạt động...");
}
@Override
public void print() {
System.out.println("Máy in thông minh đang in...");
}
}
7. Triển khai nhiều interface: vì sao điều này hữu ích
Trong Java, một class chỉ có thể kế thừa một class (abstract hoặc thường), nhưng có thể triển khai bao nhiêu interface tùy ý! Điều này cho phép tạo ra kiến trúc linh hoạt và dễ mở rộng.
public interface Scannable {
void scan();
}
public class MultiFunctionPrinter extends Machine implements Printable, Scannable {
@Override
public void work() {
System.out.println("Máy đa chức năng đang hoạt động...");
}
@Override
public void print() {
System.out.println("Máy đa chức năng đang in...");
}
@Override
public void scan() {
System.out.println("Máy đa chức năng đang quét...");
}
}
Khi nào chọn cái nào?
- Nếu bạn thiết kế chức năng nền tảng có trạng thái chung (ví dụ: field), hãy dùng abstract class.
- Nếu bạn muốn gắn cho đối tượng các “nhãn khả năng” (ví dụ: “có thể in”, “có thể so sánh”, “có thể tuần tự hóa”) — hãy dùng interface.
- Nếu chưa chắc chắn — hãy bắt đầu với interface. Trong Java đây là thói quen tốt: interface mang lại tính linh hoạt và khả năng mở rộng cao.
8. Lỗi thường gặp và điểm cần lưu ý
Lỗi số 1: Cố gắng kế thừa nhiều class — Java sẽ không cho phép!
Class chỉ có thể kế thừa một class, nhưng có thể triển khai nhiều interface. Ví dụ, class A extends B, C — lỗi, còn class A extends B implements X, Y, Z — thì được.
Lỗi số 2: Nhầm lẫn giữa field của interface và của class.
Trong interface, chỉ có thể khai báo hằng số (public static final). Không thể khai báo trạng thái thông thường, ví dụ private int count; — compiler sẽ chặn ngay.
Lỗi số 3: Không triển khai tất cả phương thức của interface.
Nếu class không triển khai dù chỉ một phương thức của interface — nó phải được khai báo là abstract, nếu không compiler sẽ báo lỗi.
Lỗi số 4: Cố tạo instance của interface hoặc abstract class.
Cả hai loại này đều là “bán thành phẩm”. Chỉ có thể mở rộng, không thể tạo trực tiếp:
Printable p = new Printable(); // Lỗi!
Machine m = new Machine(); // Lỗi!
Lỗi số 5: Nghĩ rằng interface có thể có constructor.
Interface không thể có constructor vì chúng không mô tả trạng thái của đối tượng. Chỉ class (thường và abstract) mới có.
GO TO FULL VERSION