OLHub

Câu hỏi phỏng vấn Java OOP

Bộ câu hỏi này harvest từ khoá Java OO & Functional — mỗi câu có link bài học đào sâu cơ chế, không dừng ở đáp án thuộc lòng.

Đề Java5 câuJunior–Midmỗi câu ≥1 bài học
  1. 01

    Abstract class khác interface ở điểm nào, và khi nào chọn mỗi bên?

    Junior

    Interface là hợp đồng năng lực ("can-do"): một lớp implements được nhiều interface, field chỉ là hằng public static final, và từ Java 8 có default method — thứ ra đời để thêm method vào interface đã publish mà không phá mọi class implement cũ (đó là cách JDK thêm được Collection.removeIf). Abstract class mô tả bản chất ("is-a"): giữ được state qua instance field, có constructor cho lớp con gọi super(...), và gánh được Template Method — cha khoá khung thuật toán bằng method final, con chỉ điền từng bước, như HttpServlet hay InputStream. Vì Java đơn kế thừa class, chọn abstract class là tiêu mất khe kế thừa duy nhất của lớp con — nên Effective Java (Item 20) khuyên mặc định đi từ interface, chỉ nâng lên abstract class khi các implementation thật sự chia sẻ state và logic chung. Pattern chuẩn trong JDK là kết hợp cả hai: List (interface định nghĩa kiểu) + AbstractList (skeletal implementation gánh code chung) + ArrayList (concrete).

    Trả lời thuộc lòng bảng so sánh mà không biết default method từ Java 8 đã làm mờ ranh giới — và không biết hai interface có default method trùng chữ ký sẽ ép class phải tự override, chọn tường minh bằng A.super.method().
  2. 02

    Override khác overload thế nào? Java chọn method nào lúc runtime?

    Junior

    Overload là static binding: compiler chọn method ngay lúc biên dịch dựa trên kiểu khai báo và chữ ký tham số — không có chi phí runtime. Override là dynamic dispatch: compiler chỉ sinh lệnh invokevirtual, còn JVM lúc chạy mới lần theo object trên heap, đọc class pointer để tìm vtable của kiểu thực, rồi gọi method ở đúng slot — vì vậy Animal a = new Dog() gọi a.speak() sẽ chạy bản của Dog. Ngoại lệ quan trọng: static, privatefinal method không tham gia dynamic dispatch (bind tĩnh bằng invokestatic/invokespecial, nên final method được JIT inline thẳng) — và static method cùng tên ở lớp con chỉ là "hiding", gọi qua reference kiểu cha vẫn chạy bản của cha. Override hợp lệ còn phải giữ hợp đồng: không thu hẹp visibility, không ném checked exception rộng hơn của cha, và từ Java 5 được phép covariant return type.

    Bẫy kinh điển: gọi method non-final trong constructor của cha — dynamic dispatch chọn bản của con khi field của con còn chưa khởi tạo, in ra null; và nhầm rằng kiểu của biến quyết định method — với override, kiểu thực của object mới quyết định.
  3. 03

    Trừu tượng hoá (abstraction) trong OOP thực chất là gì — ngoài định nghĩa sách giáo khoa?

    Junior

    Định nghĩa "giấu chi tiết implementation" chỉ mô tả kết quả; bản chất trừu tượng hoá là quá trình bỏ đi chi tiết không quan trọng theo ngữ cảnh và giữ lại điểm chung, để xử lý nhiều thứ khác nhau một cách đồng nhất. Vì phụ thuộc ngữ cảnh nên cùng một chiếc ô tô có nhiều abstraction đều đúng: trạm thu phí chỉ cần type, weight, plateNumber; gara lại cần engineCode, lastService — không có abstraction "đúng tuyệt đối", chỉ có đúng với use case. Trong 4 trụ cột OOP, ba cái còn lại là cơ chế có keyword riêng; abstraction là tư duy quyết định "mô hình hoá cái gì" — encapsulation chỉ là cơ chế che giấu theo abstraction đã chọn. Bài test nhanh: viết được code dùng abstraction mà không cần biết implementation cụ thể (kiểu totalBalance(List<Account>)); nếu caller phải instanceof rồi cast xuống class con để xử lý case riêng, abstraction đó đã sai.

    Trừu tượng hoá quá sớm cũng là lỗi: rule of 3 (Fowler) — chờ đủ 3 case thực tế hãy abstract, vì 2 case có thể chỉ là trùng hợp; interface chỉ có một implementation thường là dead weight chứ không phải thiết kế đẹp.
  4. 04

    Vì sao nói 'ưu tiên composition hơn inheritance'? Khi nào kế thừa vẫn hợp lý?

    Mid

    Kế thừa khiến lớp con phụ thuộc cả vào chi tiết cài đặt của cha — ví dụ kinh điển: InstrumentedHashSet extends HashSet override cả add() lẫn addAll() để đếm phần tử, nhưng addAll 3 phần tử lại đếm ra 6, vì HashSet.addAll() bên trong tự gọi add() và dynamic dispatch trỏ ngược về bản override của lớp con. Đó là fragile base class problem: cha đổi cách cài đặt là con vỡ ngầm, không báo trước. Composition thay bằng giữ reference và delegate (private final Set<E> delegate) — đếm đúng, tách hẳn khỏi implementation của cha, và swap được lúc runtime: đổi HashSet sang TreeSet chỉ là đổi constructor argument. Kế thừa vẫn hợp lý khi đủ 3 điều kiện của Effective Java (Item 18): quan hệ is-a thoả Liskov thật sự, lớp cha được thiết kế và document chủ đích cho kế thừa (như AbstractList), và bạn kiểm soát được cả hai đầu.

    Is-a 'theo ngôn ngữ thường' chưa đủ: Square is-a Rectangle đúng toán học, nhưng Square phá invariant 'setWidth/setHeight độc lập' của Rectangle (vi phạm Liskov) — bài test đúng là 'caller đang dùng lớp cha có bị bất ngờ khi nhận lớp con không'.
  5. 05

    Sealed class giải quyết vấn đề gì mà abstract class thường không làm được?

    Mid

    Trước Java 17 chỉ có hai thái cực: final (cấm hẳn kế thừa) hoặc mở cho tất cả — sealed thêm khoảng giữa: permits khai báo đúng tập lớp con hợp lệ, và mỗi lớp con buộc phải tự chọn tiếp final, sealed, hoặc non-sealed (mở lại). Giá trị lớn nhất nằm ở compile-time: vì compiler biết tập lớp con là đóng, switch pattern matching được kiểm tra exhaustive không cần default — thêm một lớp con mới vào permits là mọi switch chưa phủ case lập tức báo lỗi biên dịch, thay vì thành bug runtime như chuỗi if instanceof bị quên nhánh. Kết hợp sealed + record + switch cho Java một algebraic data type đúng nghĩa — kiểu Result<T> permits Success, Failure buộc caller xử lý cả hai nhánh mà không cần null hay exception cho flow bình thường.

    Hai câu hỏi ngược hay gặp: (1) sealed có giúp JVM nhanh hơn không — không đáng kể, HotSpot đã devirtualize bằng class hierarchy analysis và inline cache từ trước khi sealed ra đời, giá trị của sealed nằm ở compile-time; (2) chỉ cần một nhánh non-sealed là switch phải gom nó bằng một case tổng quát, exhaustive không còn tuyệt đối.

Topic kế cùng track: Câu hỏi phỏng vấn Java Collections

Trả lời trôi chảy bắt đầu từ hiểu cơ chế

Mỗi câu ở trên đều có bài học đứng sau. Học tuần tự cả khoá Java OO & Functional để không chỉ trả lời được, mà giải thích được vì sao.