Khi override method ném checked exception, subclass phải tuân thủ quy tắc covariant exception.
3 Quy tắc vàng
1. Ném cùng exception hoặc SUBTYPE (hẹp hơn) – ✅ Hợp lệ
class Parent {
void read() throws IOException {} // IOException
}
class Child extends Parent {
@Override
void read() throws FileNotFoundException {} // OK: FileNotFoundException extends IOException
}
2. KHÔNG ném SUPER TYPE (rộng hơn) – ❌ Compile error
class Parent {
void read() throws FileNotFoundException {}
}
class Child extends Parent {
@Override
void read() throws IOException {} // ERROR: IOException rộng hơn FileNotFoundException
}
// Compile error: overridden method does not throw IOException
3. Không ném exception nào – ✅ Hợp lệ
class Parent {
void read() throws IOException {}
}
class Child extends Parent {
@Override
void read() {} // OK: không ném exception
}
Tóm tắt quy tắc
| Parent throws | Child throws | Hợp lệ? |
|---|---|---|
| IOException | FileNotFoundException | ✅ (subtype) |
| IOException | IOException | ✅ (same) |
| IOException | Exception | ❌ (supertype) |
| IOException | (không có) | ✅ |
| (không có) | IOException | ❌ (parent không khai báo) |
| RuntimeException | bất kỳ | ✅ (unchecked không bị ràng buộc) |
Tại sao có quy tắc này?
Đảm bảo Liskov Substitution Principle: code dùng Parent type không bị bất ngờ bởi exception mới mà Child ném ra.
Parent p = new Child();
try {
p.read(); // Compiler chỉ biết IOException từ Parent
} catch (IOException e) { ... }
// Nếu Child ném Exception → không bị catch → crash
Unchecked Exception (RuntimeException)
Không bị ràng buộc – Child có thể ném bất kỳ RuntimeException nào, hoặc không ném.
Best Practice
- Override method: ném exception cụ thể nhất có thể (subtype)
- Dùng unchecked exception cho lỗi programming (bug), checked cho lỗi recoverable
- Interface default method: cùng quy tắc