Trong lập trình hướng đối tượng, Abstract class và Interface là hai công cụ quan trọng dùng để xây dựng kiến trúc phần mềm theo hướng mở rộng và tái sử dụng. Tuy nhiên, nhiều người mới học thường dễ bị nhầm lẫn giữa hai khái niệm này. Bài viết này sẽ giúp bạn hiểu rõ bản chất, điểm giống và khác nhau giữa chúng, từ đó biết khi nào nên dùng cái nào.
A. Abtract Class
-
Abstract class (lớp trừu tượng) là một lớp không thể tạo đối tượng trực tiếp (tức khởi tảo thông qua từ khóa new), được dùng làm lớp cơ sở cho các lớp con
-
Các lớp con kế thừa (extends) phải triển khai các phương thức abtract (phương thức trừu tượng)
-
Nó có thể chứa:
-
Các phương thức trừu tượng (chưa có nội dung, chỉ khai báo),
-
Các phương thức bình thường (có nội dung cụ thể),
-
Các thuộc tính (biến).
-
Ví dụ
public abstract class Animal { // Phương thức trừu tượng (không có thân) public abstract void makeSound(); // Phương thức bình thường public void sleep() { System.out.println("Sleeping..."); }
}
Lớp con kế thừa phải override (ghi đè) tất cả các phương thức abstract, trừ khi lớp con cũng là abstract. Tức là:
-
Nếu lớp cha có phương thức abstract (không có thân), thì lớp con bắt buộc phải triển khai (override) phương thức đó để định nghĩa rõ hành vi.
-
Nhưng nếu lớp con đó cũng được khai báo là abstract class, thì nó không cần phải override ngay các phương thức abstract đó, vì lớp con abstract có thể vẫn để lại cho lớp con tiếp theo xử lý.
public class Dog extends Animal { @Override public void makeSound() { System.out.println("Woof Woof!"); }
}
2. Một số đặc điểm
1. Abstract Class không thể tạo đối tượng trực tiếp
Bạn không thể viết:
Animal a = new Animal("Buddy"); // Lỗi biên dịch!
vì Animal là abstract, nó chưa hoàn chỉnh, không thể khởi tạo đối tượng trực tiếp.
2. Abstract Class có thể có constructor
Mặc dù không tạo đối tượng trực tiếp từ abstract class, bạn vẫn có thể khai báo constructor trong abstract class.
Constructor này sẽ được gọi khi một đối tượng của lớp con (non-abstract) được tạo ra.
abstract class Animal { String name; public Animal(String name) { this.name = name; System.out.println("Constructor của Animal được gọi, name = " + name); } abstract void makeSound();
} class Dog extends Animal { public Dog(String name) { super(name); // Gọi constructor của lớp cha (Animal) System.out.println("Constructor của Dog được gọi"); } @Override void makeSound() { System.out.println(name + " says Woof!"); }
} public class Test { public static void main(String[] args) { Dog dog = new Dog("Buddy"); // Tạo đối tượng Dog dog.makeSound(); }
}
3. Một lớp có thể chỉ định là abstract mà không có phương thức abstract nào
Ví dụ:
abstract class A { public void hello() { System.out.println("Hello from A"); }
}
- Lớp A ở trên là hợp lệ. Không có phương thức abstract nhưng vẫn là abstract class
- Lí do cho việc khai báo abstract mà không có phương thức abstract thường là:
- Lớp đó được thiết kế để không cho phép tạo trực tiếp, dù đã có đầy đủ các phương thức. Tức là nó chỉ dùng làm lớp cha để kế thừa.
- Ép buộc thiết kế hướng kế thừa (template, strategy, factory pattern...)
- Làm rõ ý đồ lập trình: "lớp này là nền tảng, không phải để dùng trực tiếp"
4. Abstract class giúp thiết kế theo hướng OOP, tạo ra khuôn mẫu bắt buộc các lớp con phải triển khai.
-
Khi bạn khai báo một hoặc nhiều phương thức là abstract, bạn đang nói: 👉 “Mọi lớp kế thừa từ lớp này phải tự định nghĩa hành vi cụ thể cho những phương thức đó.”
-
Điều này giúp đảm bảo tính nhất quán và kiểm soát thiết kế trong các hệ thống lớn.
abstract class Animal { // Phương thức bắt buộc lớp con phải định nghĩa abstract void makeSound(); // Hành vi chung có thể dùng lại void breathe() { System.out.println("Breathing..."); }
}
class Dog extends Animal { @Override void makeSound() { System.out.println("Woof!"); }
} class Cat extends Animal { @Override void makeSound() { System.out.println("Meow!"); }
}
-
Lớp Animal là khuôn mẫu: mọi "con vật" đều phải biết kêu (makeSound()), nhưng cụ thể kêu thế nào là do từng loài quyết định.
-
breathe() là hành vi chung → lớp cha định nghĩa sẵn → lớp con không cần viết lại.
Kết luận ngắn gọn:
Abstract class giống như “bản thiết kế khung” của một nhóm đối tượng — nó nói “bạn phải làm gì”, nhưng không nói “làm như thế nào”.
B. Interface
Trong Java, interface là một kiểu dữ liệu đặc biệt dùng để khai báo một tập hợp các phương thức trừu tượng (abstract methods) mà một lớp (class) có thể cam kết thực hiện.
Ví dụ:
public interface Animal { void makeSound(); // phương thức trừu tượng
}
Một class muốn triển khai (implement) một interface phải override tất cả các phương thức bên trong interface đó.
public class Dog implements Animal { @Override public void makeSound() { System.out.println("Woof!"); }
}
1. Một số đặc điểm
-
Các phương thức trong interface mặc định là public abstract (kể cả nếu không ghi rõ).
-
Các biến trong interface mặc định là public static final (hằng số).
-
Một lớp có thể implement nhiều interface → hỗ trợ đa kế thừa kiểu interface.
interface Flyable { void fly();
} interface Swimable { void swim();
} class Duck implements Flyable, Swimable { public void fly() { System.out.println("Duck flies"); } public void swim() { System.out.println("Duck swims"); }
}
2. Thay đổi của interface
1️⃣Phương thức mặc định (default) (có thân hàm trong interface)
-
Giúp cung cấp logic mặc định, không bắt buộc class triển khai phải override.
-
Cho phép thêm chức năng mới vào interface mà không làm hỏng các class cũ đang implements interface đó.
public interface PaymentMethod { void pay(double amount); // phương thức chính (bắt buộc override) // default method default void printReceipt(double amount) { System.out.println("Đã thanh toán số tiền: " + amount); System.out.println("Hẹn gặp lại quý khách!"); }
}
⚠️ Lưu ý về xung đột default method Nếu một class implements nhiều interface mà các interface đó có cùng tên default method, bạn phải override trong class.
interface A { default void hello() { System.out.println("Hello from A"); }
} interface B { default void hello() { System.out.println("Hello from B"); }
} class C implements A, B { @Override public void hello() { // Bắt buộc ghi đè để giải quyết xung đột A.super.hello(); // hoặc B.super.hello(); }
}
2️⃣Phương thức tĩnh (static):
- Trong Java, từ Java 8 trở đi, bạn có thể khai báo phương thức static trong interface
- Phương thức static trong interface chỉ được gọi qua chính tên của interface
public interface PaymentUtils { static void printSupportHotline() { System.out.println("Gọi 1800-9999 để được hỗ trợ thanh toán."); }
}
Gọi method static từ interface
public class Main { public static void main(String[] args) { PaymentUtils.printSupportHotline(); // ✅ Gọi đúng }
}
Phương thức static trong interface thường
-
Dùng để cung cấp tiện ích chung (utility) liên quan đến logic của interface.
-
Tăng tính đóng gói logic liên quan, tránh viết rải rác vào class tiện ích (utility class) như Utils.java.
3️⃣Private methods trong interface (Java 9)
- Cho phép viết private method dùng trong nội bộ default và static, giúp tái sử dụng logic và làm code gọn gàng.
- Không thể gọi từ bên ngoài, chỉ dùng nội bộ trong interface
interface PaymentMethod { default void validate() { log("Validating..."); } private void log(String message) { System.out.println("LOG: " + message); }
}
C. Tổng kết so sánh
Tiêu chí | Abstract class | Interface |
---|---|---|
Khai báo | abstract class | interface |
Hỗ trợ kế thừa (inheritance) | Chỉ được kế thừa 1 abstract class (single inheritance) | Có thể implements nhiều interface |
Hỗ trợ đa kế thừa | ⛔️ Không | ⛔️ Không (đạt được mục đích đa kế thừa thông qua implement lại nhiều interface) |
Có thể chứa phương thức abstract? | ✔️ Có | ✔️ Có |
Có thể chứa phương thức có thân hàm? | ✔️ Có (normal method) | ✔️ Có default, static (Java 8+) |
Có thể chứa private method? | ✔️ Có | ✔️ Có (từ Java 9+) |
Có thể chứa biến (field)? | ✔️ Có (instance + static + final) | ✔️ public static final (hằng số) |
Constructor | ✔️ Có thể có constructor | ⛔️ Không thể có constructor |
Access modifier | Cho phép: public, protected, private, | Chỉ cho phép: public, private (Java 9+), default, static |
Trong lập trình Java, cả abstract class và interface đều là những công cụ quan trọng để thiết kế kiến trúc hệ thống một cách linh hoạt và có tính mở rộng cao. Tuy có điểm tương đồng – đều cho phép định nghĩa các phương thức trừu tượng – nhưng mỗi loại phục vụ những mục đích khác nhau:
-
Abstract class thích hợp khi bạn cần chia sẻ logic, trạng thái hoặc cấu trúc chung giữa các lớp.
-
Interface sử dụng khi muốn tạo ra một sườn chung cho hệ thống mà khi các lớp implement lại phải tuân theo, đặc biệt khi cần hỗ trợ đa kế thừa hoặc tích hợp với các tính năng như Lambda trong Java 8+.
Java ngày càng linh hoạt với nhiều cải tiến từ Java 8 trở đi, nên ranh giới giữa hai khái niệm này có thể mờ dần. Tuy nhiên, việc lựa chọn đúng công cụ vẫn là chìa khóa để xây dựng hệ thống mạnh mẽ, mở rộng tốt và dễ bảo trì.
Chọn đúng giữa interface và abstract class – không chỉ là lập trình, mà là thiết kế.