Java cấm class kế thừa nhiều class, nhưng cho phép class implement nhiều interface. Đây là cơ chế giải quyết vấn đề "một class có nhiều vai trò": ArrayList là List, là Collection, là Iterable, là Serializable, là Cloneable. Không cây kế thừa nào đủ để biểu diễn tất cả.
Interface từ Java 8 thêm default method (có body mặc định), Java 9 thêm private method — làm nó gần với abstract class. Bài này phân biệt rõ lúc nào chọn interface, cú pháp hiện đại, và pattern functional interface cho lambda.
1. Analogy — chứng chỉ kỹ năng
Bạn có thể có chứng chỉ lái xe, chứng chỉ tiếng Anh TOEIC 900, chứng chỉ bơi lội. Mỗi chứng chỉ chứng minh một kỹ năng — nhà tuyển dụng xem chứng chỉ bạn có để quyết định giao việc gì. Chứng chỉ không phải là "bạn là gì" — nó là "bạn làm được gì".
| Đời thường | Java |
|---|---|
| Chứng chỉ kỹ năng | interface |
| Người có nhiều chứng chỉ | Class implement nhiều interface |
| Tờ chứng chỉ liệt kê yêu cầu | Method abstract trong interface |
| Yêu cầu có hướng dẫn chung | default method |
💡 💡 Cách nhớ
Class là gì (kế thừa) vs class làm được gì (interface). Đặt câu hỏi này khi thiết kế: nếu câu "làm được gì" hợp lý hơn, dùng interface.
2. Cú pháp interface
public interface Comparable<T> {
int compareTo(T other); // abstract method — khong body
}
Class implement:
public class Version implements Comparable<Version> {
private final int major;
private final int minor;
public Version(int major, int minor) {
this.major = major;
this.minor = minor;
}
@Override
public int compareTo(Version other) {
int cmp = Integer.compare(major, other.major);
return cmp != 0 ? cmp : Integer.compare(minor, other.minor);
}
}
Rule interface cơ bản:
- Method mặc định
public abstract— không cần gõ keyword. - Field mặc định
public static final— interface không có instance field. - Không có constructor.
- Không
newđược interface trực tiếp.
public interface Greeter {
String DEFAULT = "Hi"; // ngam public static final
void greet(); // ngam public abstract
}
3. Đa kế thừa interface
Một class implement nhiều interface:
public interface Runnable { void run(); }
public interface Callable<V> { V call(); }
public interface AutoCloseable { void close(); }
public class Task implements Runnable, Callable<String>, AutoCloseable {
@Override public void run() { ... }
@Override public String call() { return "done"; }
@Override public void close() { ... }
}
Interface kế thừa interface khác:
public interface Collection<E> extends Iterable<E> { ... }
public interface List<E> extends Collection<E> { ... }
List kế thừa mọi method của Collection và Iterable. Class implement List phải implement mọi abstract method từ cả ba.
Đa kế thừa interface tránh diamond problem của đa kế thừa class vì interface chỉ có hợp đồng, không có state — "không có gì để conflict".
4. default method (Java 8+) — body mặc định
Trước Java 8, thêm method mới vào interface phá mọi class đã implement. Java 8 giải quyết bằng default:
public interface List<E> extends Collection<E> {
// method abstract — moi implement phai override
E get(int index);
E set(int index, E element);
// default method — tu cho san body
default void forEach(Consumer<? super E> action) {
for (E e : this) action.accept(e);
}
default List<E> subList(int from, int to) {
return new SubList<>(this, from, to);
}
}
Class implement List không bắt buộc override forEach / subList — lấy phiên bản default của interface. Muốn thay đổi behaviour thì override.
Hệ quả: JDK thêm method mới vào interface cũ mà không phá backward compatibility. Collection.stream(), List.forEach, Map.getOrDefault — đều là default method Java 8+.
4.1 Diamond với default method — giải quyết thủ công
interface A {
default String name() { return "A"; }
}
interface B {
default String name() { return "B"; }
}
class C implements A, B {
// class can giai quyet xung dot
@Override
public String name() {
return A.super.name() + "/" + B.super.name(); // chon cach xu ly
}
}
Class implement 2 interface có cùng default method → compile error nếu không resolve. Class phải override, có thể dùng A.super.method() để gọi phiên bản cụ thể.
5. static method trong interface (Java 8+)
public interface StringUtils {
static boolean isBlank(String s) {
return s == null || s.isEmpty();
}
}
StringUtils.isBlank(""); // OK
Method static trong interface gọi qua tên interface, không kế thừa. Giống static method trong class — dispatch tĩnh.
Phổ biến cho factory method: List.of, Map.of, Stream.empty, Path.of.
6. private method trong interface (Java 9+)
public interface Validator<T> {
default boolean isValid(T value) {
if (value == null) return false;
return check(value);
}
default boolean isValidStrict(T value) {
if (!isValid(value)) return false;
return check(value) && additionalCheck(value);
}
private boolean check(T value) { ... } // helper private
private boolean additionalCheck(T value) { ... }
boolean validate(T value);
}
private method trong interface là helper — chỉ dùng bên trong interface bởi default method khác. Không thấy được từ class implement. Dùng để tránh lặp code giữa nhiều default method.
7. Functional interface — lambda + method reference
Interface có đúng 1 abstract method gọi là functional interface. Java 8 cho phép dùng lambda hoặc method reference thay vì anonymous class:
@FunctionalInterface
public interface Calculator {
int compute(int a, int b);
}
// Trước Java 8 — anonymous class
Calculator add = new Calculator() {
@Override public int compute(int a, int b) { return a + b; }
};
// Java 8 — lambda
Calculator add = (a, b) -> a + b;
// Java 8 — method reference
Calculator add = Integer::sum;
@FunctionalInterface annotation không bắt buộc — chỉ check compile: nếu interface có ≠ 1 abstract method, báo lỗi. Dùng khi bạn chủ ý thiết kế cho lambda.
Các functional interface phổ biến của JDK (package java.util.function):
Predicate<T> boolean test(T t);
Function<T, R> R apply(T t);
Consumer<T> void accept(T t);
Supplier<T> T get();
BiFunction<T, U, R> R apply(T t, U u);
Stream API, Optional, CompletableFuture... đều nhận các interface này.
8. Interface vs Abstract class — khi nào chọn cái nào?
| Tiêu chí | Interface | Abstract class |
|---|---|---|
| Có state (instance field) | ❌ | ✅ |
| Constructor | ❌ | ✅ |
| Đa kế thừa | ✅ | ❌ |
| Mục đích | "Can-do" capability | "Is-a" với share state |
| Default implementation | default method (Java 8+) | Method concrete bình thường |
| Dùng với lambda | ✅ (functional) | ❌ |
Rule Effective Java (Item 20): ưu tiên interface hơn abstract class. Lý do:
- Class chỉ đơn kế thừa — abstract class "khoá" class con khỏi extends class khác.
- Interface cho phép class đã tồn tại (không kiểm soát được) thêm capability qua retrofit.
- Java 8
defaultmethod làm interface gần đủ mạnh cho nhiều use case trước kia phải abstract class.
Abstract class vẫn cần khi:
- Share state phức tạp (field nhiều, tương tác với nhau).
- Constructor logic phức tạp — interface không có constructor.
- Bạn muốn tạo skeletal implementation — kết hợp với interface để giảm boilerplate cho implement (ví dụ
AbstractListchoList).
9. Pattern: Interface + Skeletal abstract class
public interface Shape {
double area();
double perimeter();
default String describe() {
return getClass().getSimpleName() + ": area=" + area() + ", perimeter=" + perimeter();
}
}
public abstract class AbstractShape implements Shape {
private final String name;
protected AbstractShape(String name) { this.name = name; }
public String getName() { return name; }
@Override
public String describe() {
return name + ": area=" + area() + ", perimeter=" + perimeter();
}
}
public class Circle extends AbstractShape {
private 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 Ring implements Shape {
// neu khong muon extends AbstractShape, van implement duoc truc tiep
private double inner, outer;
@Override public double area() { return Math.PI * (outer * outer - inner * inner); }
@Override public double perimeter() { return 2 * Math.PI * (inner + outer); }
}
Người dùng chọn: extends AbstractShape cho tiện (nhận name, describe chuẩn), hoặc implements Shape trực tiếp khi đã có cha khác.
Pattern này JDK dùng khắp: List + AbstractList, Map + AbstractMap, Set + AbstractSet.
10. Pitfall tổng hợp
❌ Nhầm 1: Tưởng interface có field.
public interface Config {
int timeout = 30; // thuc te la public static final — khong the thay doi
}
✅ Hiểu rõ: field trong interface mặc định public static final. Muốn state instance → abstract class.
❌ Nhầm 2: Quên @Override cho method interface.
class Task implements Runnable {
public void run() { ... } // method, khong annotation
}
✅ @Override — bắt lỗi typo signature ngay.
❌ Nhầm 3: Thêm abstract method vào interface đã publish.
// Library v1:
public interface Animal { void eat(); }
// Library v2:
public interface Animal {
void eat();
void sleep(); // PHA moi class implement cu
}
✅ Dùng default:
default void sleep() { /* default impl */ }
❌ Nhầm 4: Diamond default method không resolve.
class C implements A, B { } // A va B deu co default foo() -> COMPILE ERROR
✅ Override trong C hoặc dùng A.super.foo() / B.super.foo().
❌ Nhầm 5: Dùng interface cho mọi thứ — "ưu tiên interface" không phải "luôn interface".
interface Employee { String getName(); String getEmail(); ... 20 method }
✅ Nếu data holder thuần, dùng record. Interface cho capability, record cho data.
11. 📚 Deep Dive Oracle
ℹ️ 📚 Deep Dive Oracle (optional)
Spec / reference chính thức:
- JLS §9 — Interfaces — toàn bộ rule.
- JLS §9.4 — Method Declarations — rule cho default/static/private method.
- JEP 335 — default method — motivation và design.
- JEP 213 — Private Methods in Interfaces — Java 9+.
- java.util.function package — bộ functional interface chuẩn.
- Effective Java Item 20: "Prefer interfaces to abstract classes"; Item 21: "Design interfaces for posterity".
Ghi chú: Item 21 của Effective Java cảnh báo về default method — chúng giúp thêm method mà không phá backward compat, nhưng behaviour mặc định có thể không phù hợp cho mọi implementation. Thiết kế default method phải cẩn thận: dùng cho hành vi có thể diễn đạt thuần tuý qua method abstract khác, tránh đoán ngầm.
12. Tóm tắt
- Interface = hợp đồng capability. Class implement nhiều interface được; interface extends nhiều interface được.
- Method trong interface mặc định
public abstract; field mặc địnhpublic static final. defaultmethod (Java 8+) cung cấp body mặc định — giúp thêm method vào interface cũ không phá backward compat.staticmethod trong interface — helper/factory, gọi qua tên interface.privatemethod (Java 9+) — helper cho default method trong cùng interface.- Functional interface = 1 abstract method → dùng với lambda / method reference.
@FunctionalInterfaceannotation check compile. - JDK functional interface:
Predicate,Function,Consumer,Supplier,BiFunction... - Đa kế thừa default method → compile error nếu conflict → class phải override, dùng
A.super.foo()để chọn. - Ưu tiên interface trừ khi cần share state — dùng abstract class hoặc skeletal implementation.
- Pattern Interface + Skeletal: JDK
List + AbstractList.
13. Tự kiểm tra
Q1Đoạn sau compile không?interface A {
default String name() { return "A"; }
}
interface B {
default String name() { return "B"; }
}
class C implements A, B { }
▸
interface A {
default String name() { return "A"; }
}
interface B {
default String name() { return "B"; }
}
class C implements A, B { }Không. Class C implement cả A và B — cả hai có default method name() khác nhau. Compiler không chọn được phiên bản nào → compile error: "class C inherits unrelated defaults for name() from types A and B".
Fix: C phải override và quyết định logic:
class C implements A, B {
@Override
public String name() {
return A.super.name(); // chon A
// hoac: return B.super.name();
// hoac: return A.super.name() + "/" + B.super.name();
// hoac: return "C";
}
}Cú pháp A.super.name() để gọi tường minh phiên bản của A. Java ép class giải quyết diamond thủ công — an toàn, rõ ràng.
Q2Đoạn sau có gì đáng chú ý?public interface Config {
int TIMEOUT = 30;
}
Config.TIMEOUT = 60; // compile?
▸
public interface Config {
int TIMEOUT = 30;
}
Config.TIMEOUT = 60; // compile?Dòng Config.TIMEOUT = 60 không compile. Field trong interface mặc định public static final — là constant, không gán lại được.
Bẫy: nhiều người tưởng int TIMEOUT = 30 trong interface là instance field. Thực ra:
public— truy cập khắp nơi.static— thuộc interface, không thuộc instance (interface không có instance).final— không gán lại.
Nếu cần "state" liên quan đến config thay đổi được, dùng class thường với field mutable. Interface không cho state.
Q3Đoạn sau là functional interface không? Dùng với lambda được không?@FunctionalInterface
interface Checker {
boolean check(String input);
default boolean checkAll(List<String> inputs) {
return inputs.stream().allMatch(this::check);
}
static Checker notBlank() {
return s -> s != null && !s.isBlank();
}
}
▸
@FunctionalInterface
interface Checker {
boolean check(String input);
default boolean checkAll(List<String> inputs) {
return inputs.stream().allMatch(this::check);
}
static Checker notBlank() {
return s -> s != null && !s.isBlank();
}
}Có. Functional interface = đúng 1 abstract method. Checker có:
- 1 abstract method:
boolean check(String input). - 1 default method:
checkAll— không tính vào rule. - 1 static method:
notBlank— không tính.
@FunctionalInterface annotation đảm bảo compile-check rule. Dùng với lambda:
Checker isEmail = s -> s.contains("@");
Checker notBlank = Checker.notBlank(); // qua static factory
System.out.println(isEmail.check("[email protected]")); // true
System.out.println(isEmail.checkAll(List.of("a@b", "c@d"))); // true
// Cung dung method reference:
Checker nonEmpty = String::isEmpty; // WRONG — signature khac
// Phai: nonEmpty = s -> !s.isEmpty(); hoac Predicate<String>.negateQ4Khi nào nên thêm default method vào interface và khi nào không nên?▸
default method vào interface và khi nào không nên?Nên thêm khi:
- Method mới với behaviour mặc định hợp lý cho hầu hết implementation — vd
Collection.stream()default tạo sequential stream từ iterator. - Method derivative tính toán từ method abstract khác — vd
default boolean isNotEmpty() { return !isEmpty(); }. - Evolve interface cũ mà không phá backward compat.
KHÔNG nên khi:
- Behaviour mặc định không thể diễn đạt từ các abstract method khác — thường dẫn đến logic sai ngầm cho subclass.
- Method liên quan đến state — interface không có state, default method truy cập state phải qua abstract accessor, dễ sinh performance issue.
- Cần đa override phức tạp — nếu default method gọi nhiều abstract method khác, order chạy dễ sai (tương tự bẫy constructor). Skeletal abstract class tốt hơn.
JDK sử dụng default method rất cẩn trọng — chủ yếu cho stream API và "convenience over existing abstract". Tránh "default method cho mọi thứ".
Q5So sánh List + AbstractList — vì sao JDK có cả 2?▸
List + AbstractList — vì sao JDK có cả 2?List là interface — hợp đồng: get, set, size, add, remove... Ai cũng implement được, class đã có cha khác vẫn implement được.
AbstractList là skeletal abstract class — implement hầu hết method của List, chỉ để lại 2 method abstract cốt lõi (get(int), size()). Nếu bạn có thể cung cấp 2 method đó, AbstractList cung cấp miễn phí: iterator, indexOf, contains, equals, hashCode, toString, subList...
Pattern sử dụng:
- Có thể
extends AbstractList→ rút ngắn code. Dùng khi class chưa có cha khác. - Phải
implements Listtrực tiếp (vì đãextendsclass khác) → viết hết method hoặc wrapAbstractList.
Giá trị: user có flexibility (interface không ép cây kế thừa) + convenience (abstract class giảm boilerplate). Đây là pattern chuẩn — áp dụng cho mọi interface "to" bạn thiết kế.
Bài tiếp theo: Sealed class — kiểm soát chính xác subclass + exhaustive pattern matching