Bạn muốn viết class Shape chung cho Circle, Square, Triangle — cả 3 đều có method area(), perimeter(), nhưng công thức tính khác nhau. Class Shape không có công thức riêng, cũng không nên tồn tại như object cụ thể (ai tạo ra "shape" mà không phải hình gì?). Đây là lúc cần abstract class.
Abstract class là class không instantiate được và có thể chứa abstract method (method không body). Subclass bắt buộc implement các abstract method — compiler đảm bảo. Bài này giải thích khi nào chọn abstract class, pattern template method, và phân biệt với interface.
1. Analogy — bản thiết kế kiến trúc nhà
Bạn có bản thiết kế nhà mẫu — gồm cấu trúc tường, mái, điện, nước. Nhưng "vị trí cửa sổ" bản vẽ ghi "tuỳ chủ nhà". Bản thiết kế này không xây được thành ngôi nhà thật — phải có chủ nhà điền phần còn lại. Mỗi chủ xây cùng bản thiết kế ra nhà khác nhau ở phần "cửa sổ".
| Đời thường | Java |
|---|---|
| Bản thiết kế mẫu | abstract class House |
| Tường, mái đã vẽ | Method concrete (có body) |
| "Vị trí cửa sổ: tuỳ chủ" | abstract method (không body) |
| Ngôi nhà cụ thể | Subclass concrete |
💡 💡 Cách nhớ
Abstract class = bộ khung buộc subclass điền các lỗ hổng. Không tạo instance trực tiếp; chỉ tạo qua subclass concrete.
2. Cú pháp
public abstract class Shape {
private final String name;
protected Shape(String name) {
this.name = name;
}
public String getName() { return name; }
public abstract double area(); // abstract — khong body
public abstract double perimeter();
public String describe() { // concrete — co body, dung abstract method
return name + ": area=" + area() + ", perimeter=" + perimeter();
}
}
Subclass phải implement abstract method:
public class Circle extends Shape {
private final double radius;
public Circle(double radius) {
super("Circle");
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
@Override
public double perimeter() {
return 2 * Math.PI * radius;
}
}
public class Square extends Shape {
private final double side;
public Square(double side) {
super("Square");
this.side = side;
}
@Override
public double area() { return side * side; }
@Override
public double perimeter() { return side * 4; }
}
Dùng:
Shape s1 = new Circle(5);
Shape s2 = new Square(4);
System.out.println(s1.describe()); // Circle: area=78.53..., perimeter=31.41...
System.out.println(s2.describe()); // Square: area=16.0, perimeter=16.0
Shape s3 = new Shape("x"); // COMPILE ERROR — cannot instantiate abstract class
3. Quy tắc abstract class
3.1 Class đánh dấu abstract — không new được
Compiler bắt new Shape(...). Class vẫn có constructor — nhưng chỉ gọi được bằng super(...) từ subclass.
3.2 Chứa abstract method → class phải abstract
public class Shape {
public abstract double area(); // COMPILE ERROR — class khong abstract
}
Fix: thêm abstract trước class.
3.3 Abstract class có thể không có abstract method nào
public abstract class BaseEntity {
protected Long id;
protected LocalDateTime createdAt;
// khong co abstract method — nhung van abstract
}
Dùng khi: bạn muốn cấm instantiate (class "base" thuần tuý), nhưng không muốn buộc subclass override gì cụ thể. Pattern hiếm hơn nhưng hợp lệ.
3.4 Subclass không implement đủ → subclass phải abstract
abstract class Shape {
public abstract double area();
public abstract double perimeter();
}
abstract class Ellipse extends Shape {
private double a, b;
@Override
public double area() { return Math.PI * a * b; }
// khong implement perimeter -> Ellipse van abstract
}
class Circle extends Ellipse {
private double r;
@Override
public double perimeter() { return 2 * Math.PI * r; }
// lop nay da implement ca 2 -> concrete (co the new)
}
Subclass "truyền gánh nặng" cho cháu — hợp lệ nhưng thường là dấu hiệu nên nhóm lại.
4. Template Method Pattern
Abstract class với mix concrete method + abstract method là nền của template method pattern: cha định nghĩa thuật toán khung, gọi các bước trừu tượng; con cung cấp các bước cụ thể.
public abstract class DataExporter {
public final void export(List<Record> records) { // method final — khong override khung
String header = buildHeader();
String body = records.stream()
.map(this::formatRecord) // goi abstract method
.collect(Collectors.joining("\n"));
String footer = buildFooter();
writeFile(header + "\n" + body + "\n" + footer);
}
// Moi subclass customize 2 buoc
protected abstract String formatRecord(Record r);
protected abstract String buildHeader();
protected String buildFooter() { return ""; } // mac dinh empty, optional override
private void writeFile(String content) { ... } // private — khung quan ly
}
public class CsvExporter extends DataExporter {
@Override
protected String buildHeader() { return "id,name,email"; }
@Override
protected String formatRecord(Record r) {
return r.id + "," + r.name + "," + r.email;
}
}
public class JsonExporter extends DataExporter {
@Override
protected String buildHeader() { return "["; }
@Override
protected String formatRecord(Record r) {
return "{\"id\":" + r.id + ", \"name\":\"" + r.name + "\"}";
}
@Override
protected String buildFooter() { return "]"; }
}
Ưu điểm: luồng chung ở cha, biến thể ở con. Framework Java đầy ví dụ: javax.servlet.HttpServlet, AbstractList, InputStream.
5. Abstract class vs Interface — so sánh
| Abstract class | Interface | |
|---|---|---|
| State (instance field) | ✅ | ❌ (chỉ static final) |
| Constructor | ✅ | ❌ |
| Kế thừa | Đơn (1 cha) | Đa (nhiều interface) |
| Visibility | Bất kỳ | Method mặc định public |
| Abstract method | ✅ | ✅ |
| Default method | Method concrete bình thường | default method (Java 8+) |
static method | ✅ | ✅ (Java 8+) |
| Mục đích | "Is-a" với share state | "Can-do" capability |
Khi nào chọn abstract class?
✅ Abstract class khi:
- Có state chung subclass dùng (
name,createdAt). - Có implementation chung không muốn lặp (template method).
- Quan hệ "is-a" rõ:
Circle extends Shape(hình tròn là một loại hình).
✅ Interface khi:
- Muốn nhóm class không cùng cây kế thừa có chung capability:
Comparable,Serializable,Closeable. - Không cần state — chỉ hợp đồng method.
- Muốn class implement nhiều "vai trò".
Modern Java (8+): interface cũng có default method, nên đôi khi ranh giới mờ. Rule đơn giản: cần state → abstract class; chỉ method → interface. Nhiều team hiện ưu tiên interface trừ khi thực sự cần abstract class.
6. protected constructor — pattern thường gặp
Abstract class thường dùng protected cho constructor:
public abstract class Shape {
protected Shape(String name) { ... }
}
Lý do:
- Không cần
public— không ai bên ngoàinewđược class abstract. protectedvừa đủ để subclass gọisuper(...).- Tín hiệu rõ ý đồ cho reader: "class này chỉ extends".
7. Abstract class kèm sealed (Java 17+)
Hạn chế subclass chỉ trong tập xác định:
public sealed abstract class Shape permits Circle, Square, Triangle {
public abstract double area();
}
public final class Circle extends Shape { ... }
public final class Square extends Shape { ... }
public final class Triangle extends Shape { ... }
// class Hexagon extends Shape { ... } // COMPILE ERROR — khong trong permits
Kết hợp với switch pattern matching: compiler biết exhaustive, không cần default:
double area = switch (s) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Square sq -> sq.side() * sq.side();
case Triangle t -> 0.5 * t.base() * t.height();
// khong can default — compiler biet 3 case la day du
};
Sẽ đào sâu ở bài 5.
8. Pitfall tổng hợp
❌ Nhầm 1: new abstract class.
Shape s = new Shape("abc"); // COMPILE ERROR
✅ Chỉ new subclass concrete.
❌ Nhầm 2: Có abstract method mà quên khai class abstract.
public class Shape { // COMPILE ERROR
public abstract double area();
}
✅ Thêm abstract trước class.
❌ Nhầm 3: Abstract method có body.
public abstract double area() { return 0; } // COMPILE ERROR
✅ Abstract method không có { }.
❌ Nhầm 4: Subclass concrete không implement đủ abstract method.
abstract class Shape { public abstract double area(); }
class Circle extends Shape { } // COMPILE ERROR — chua implement area()
✅ Implement hoặc khai Circle cũng abstract.
❌ Nhầm 5: Dùng abstract class cho "capability" thuần — vì bỏ qua sức mạnh của multi-interface.
abstract class Printable { public abstract void print(); }
// class Book extends Printable — OK, nhung class Book da extends PaperItem -> COMPILE ERROR
✅ interface Printable { void print(); } — class implement được nhiều interface.
9. 📚 Deep Dive Oracle
ℹ️ 📚 Deep Dive Oracle (optional)
Spec / reference chính thức:
- JLS §8.1.1.1 — abstract Classes — rule abstract class.
- JLS §8.4.3.1 — abstract Methods — rule abstract method.
- Effective Java Item 20: Prefer interfaces to abstract classes — lý luận của Bloch.
- Gang of Four: Template Method Pattern — design pattern classic.
Ghi chú: Item 20 của Effective Java (Bloch) đề nghị mạnh: "ưu tiên interface hơn abstract class". Lý do chính: Java đơn kế thừa class → abstract class khoá class con khỏi kế thừa class khác. Interface không hạn chế đó. Với Java 8+ có default method, interface đã đủ mạnh cho hầu hết use case. Abstract class vẫn cần khi bạn thực sự muốn share state — interface không cho.
10. Tóm tắt
abstract classkhông instantiate được trực tiếp.new Shape()→ compile error.abstract methodkhông có body, buộc subclass concrete implement.- Class có abstract method → class phải abstract. Class abstract có thể không có abstract method nào.
- Abstract class có state (field), constructor, concrete method — khác với interface ở điểm này.
- Template method pattern: cha định nghĩa
finalkhung, gọi abstract method; con cung cấp bước cụ thể. - Constructor abstract class thường
protected— tín hiệu "chỉ extends". - So với interface: abstract class khi cần state + đơn kế thừa; interface khi nhóm capability không cùng cây.
- Modern Java ưu tiên interface; chỉ dùng abstract class khi thực sự cần state chung.
sealed abstract class(Java 17+) cho phép kiểm soát subclass chính xác + pattern matching exhaustive.
11. Tự kiểm tra
Q1Đoạn sau compile không?public abstract class Vehicle {
public abstract int maxSpeed();
}
public class Bike extends Vehicle { }
Vehicle v = new Bike();
▸
public abstract class Vehicle {
public abstract int maxSpeed();
}
public class Bike extends Vehicle { }
Vehicle v = new Bike();Không compile. Bike extends Vehicle nhưng không implement abstract method maxSpeed(). Compiler báo: "Bike is not abstract and does not override abstract method maxSpeed() in Vehicle".
Fix (chọn 1):
- Implement trong
Bike:public int maxSpeed() { return 25; }. - Đánh dấu
Bikecũng abstract:public abstract class Bike extends Vehicle {}. Sau đó một subclass concrete (vdMountainBike) phải implement.
Q2Khi nào chọn abstract class, khi nào chọn interface?▸
Abstract class khi:
- Bạn cần state chung — field chia sẻ mà mọi subclass inherit:
id,name,createdAt. - Bạn muốn template method pattern — khung thuật toán ở cha, subclass chỉ cung cấp bước cụ thể.
- Quan hệ "is-a" rõ và subclass thực sự là loại con cụ thể:
Circle extends Shape. - Bạn muốn cấm instantiate class base.
Interface khi:
- Chỉ cần hợp đồng method, không có state.
- Nhóm các class không cùng cây kế thừa nhưng chia sẻ một capability:
Comparable,Iterable,Serializable,AutoCloseable. - Cần đa kế thừa — class implement được nhiều interface.
- Muốn API mở mạnh cho nhiều implement, không ép cây kế thừa.
Modern Java rule: ưu tiên interface. Chỉ chọn abstract class khi thực sự cần share state hoặc khung template-method phức tạp.
Q3Đoạn sau in gì?abstract class Base {
public Base() { step(); }
public abstract void step();
}
class Sub extends Base {
private int x = 10;
@Override public void step() { System.out.println("x = " + x); }
}
new Sub();
▸
abstract class Base {
public Base() { step(); }
public abstract void step();
}
class Sub extends Base {
private int x = 10;
@Override public void step() { System.out.println("x = " + x); }
}
new Sub();In x = 0.
Thứ tự khởi tạo:
- JVM cấp memory, zero-out field
Sub.x=0. new Sub()chạy → ngầm gọisuper()→Base().Base()gọistep(). Dynamic dispatch chọnSub.step()(kiểu thực làSub).Sub.step()đọcx—xvẫn0(chưa gán10).Base()return. Giờ Java gán field initializer:x = 10.
Bài học: không gọi abstract method trong constructor — bẫy khó debug. Pattern này phổ biến đến mức có tên riêng: "partially constructed object". Fix: gọi method từ factory sau khi object đã khởi tạo xong.
Q4Viết abstract class Shape với describe() dùng abstract area(), perimeter(). Sau đó Circle concrete.▸
Shape với describe() dùng abstract area(), perimeter(). Sau đó Circle concrete.public abstract class Shape {
private final String name;
protected Shape(String name) { this.name = name; }
public abstract double area();
public abstract double perimeter();
public String describe() {
return String.format("%s: area=%.2f, perimeter=%.2f",
name, area(), perimeter());
}
}
public class Circle extends Shape {
private final double radius;
public Circle(double radius) {
super("Circle");
if (radius <= 0) throw new IllegalArgumentException("radius must be positive");
this.radius = radius;
}
@Override
public double area() { return Math.PI * radius * radius; }
@Override
public double perimeter() { return 2 * Math.PI * radius; }
}Các điểm đáng nhớ:
- Constructor
Shapeprotected: subclass gọi quasuper("Circle"); không ai bên ngoàinew Shape(...). - Field
nameprivate final: chỉ set qua constructor, subclass không access trực tiếp — getter nếu cần. describe()là template method — implementation cha, gọi abstract method của con. Nếu muốn cấm overridedescribe, thêmfinal.- Validate
radiustrong constructor con, throwIllegalArgumentException. area()/perimeter()có@Override.
Q5Abstract class có constructor — ai gọi nó và khi nào?▸
Abstract class có constructor như class thường, nhưng không ai new abstract class trực tiếp. Constructor được gọi chỉ qua super(...) từ subclass concrete.
Flow khi new Circle(5):
- JVM cấp memory cho object
Circle. Circle(5)chạy, gọisuper("Circle")→Shape("Circle").Shape("Circle")gọisuper()→Object().Object()return →Shapegánthis.name = "Circle".Shapereturn →Circlegánthis.radius = 5.- Reference object
Circlehoàn chỉnh trả về caller.
Convention: constructor abstract class thường protected — không cần public vì không ai call trực tiếp, protected đủ cho subclass.
Bài tiếp theo: Interface — hợp đồng capability, default method, và đa kế thừa có kiểm soát