Luyện Phỏng Vấn IT — 2000+ Câu Hỏi Phỏng Vấn IT Có Đáp Án 2026

== so sánh địa chỉ bộ nhớ (reference), kiểm tra xem hai biến có trỏ đến cùng một đối tượng không. .equals() so sánh nội dung/giá trị thực sự của đối tượng.

Ví dụ: String a = "hello"; String b = new String("hello"); thì a == b trả về false (khác object), nhưng a.equals(b) trả về true (cùng nội dung). Luôn dùng .equals() khi so sánh giá trị String và Object.

Không, Java không thuần hướng đối tượng vì vẫn tồn tại các kiểu nguyên thủy (primitive types) như int, float, double, boolean, char... bên cạnh các đối tượng.

  • Một ngôn ngữ OOP thuần túy phải coi mọi thứ là đối tượng.
  • Java giữ primitive types vì lý do hiệu năng và tiết kiệm bộ nhớ — thao tác với int nhanh hơn rất nhiều so với dùng Integer.

Stack lưu trữ các giá trị nguyên thủy và tham chiếu đến đối tượng, được cấp phát riêng cho mỗi thread, hoạt động theo thứ tự LIFO — khi method kết thúc, bộ nhớ stack được giải phóng tự động.

  • Heap lưu trữ các đối tượng thực sự, được chia sẻ giữa tất cả thread, và cần Garbage Collector để dọn dẹp.
  • Stack nhanh hơn nhưng giới hạn kích thước; Heap lớn hơn nhưng chậm hơn và cần quản lý GC.
  1. Hướng đối tượng: xây dựng quanh class và object, tái sử dụng code.
  2. Độc lập nền tảng: bytecode chạy trên bất kỳ JVM nào, bất kể hệ điều hành.
  3. Robust: kiểm tra kiểu chặt, exception handling, không có con trỏ giảm lỗi crash.
  4. Multithreaded: hỗ trợ lập trình đa luồng tích hợp sẵn.
  5. Bảo mật: sandbox JVM, bytecode verification, không truy cập bộ nhớ trực tiếp

String immutable vì ba lý do chính:

  1. String pool — các chuỗi giống nhau dùng chung bộ nhớ, tiết kiệm heap.
  2. Thread safety — immutability loại bỏ nhu cầu đồng bộ hóa khi dùng chung.
  3. HashMap key an toàn — nếu String key có thể thay đổi, kết quả tìm kiếm sẽ sai

Ngoài ra, immutability còn mang lại lợi ích bảo mật — dữ liệu nhạy cảm (password, URL) không bị thay đổi ngoài ý muốn.

String là immutable — mỗi lần thay đổi tạo ra object mới, không hiệu quả khi nối chuỗi liên tục.

  • StringBuilder là mutable, nhanh hơn String khi nối chuỗi nhưng KHÔNG thread-safe.
  • StringBuffer là mutable và thread-safe (các method được synchronized) nhưng chậm hơn StringBuilder một chút.
  • Dùng StringBuilder cho code đơn luồng, StringBuffer khi cần an toàn đa luồng.

Constructor overloading cho phép một class có nhiều constructor cùng tên nhưng khác nhau về danh sách tham số (khác kiểu, số lượng, hoặc thứ tự tham số). Điều này giúp khởi tạo object linh hoạt theo nhiều cách khác nhau.

Ví dụ: public Box(int size)public Box(int length, int width) là hai constructor overloaded — compiler chọn đúng constructor dựa vào tham số được truyền vào.

Access modifier kiểm soát phạm vi truy cập của class, method, và biến:

  1. public: truy cập được từ mọi nơi.
  2. private: chỉ truy cập được trong cùng class.
  3. protected: truy cập được trong cùng package và từ subclass.
  4. default (không có modifier): chỉ truy cập trong cùng package

Nguyên tắc: luôn chọn modifier hạn chế nhất có thể để đảm bảo encapsulation tốt.

Method overloading: cùng class, cùng tên method nhưng khác tham số — compile-time polymorphism (tĩnh).

  • Method overriding: subclass cung cấp implementation khác cho method của parent class với cùng signature — runtime polymorphism (động).
  • Overloading được giải quyết lúc biên dịch; overriding được giải quyết lúc chạy dựa trên kiểu thực của object.
  1. Encapsulation (Đóng gói): gom data và method vào class, ẩn chi tiết nội bộ bằng access modifier.
  2. Inheritance (Kế thừa): class con kế thừa từ class cha, tái sử dụng code.
  3. Polymorphism (Đa hình): cùng interface nhưng hành vi khác nhau (overloading/overriding).
  4. Abstraction (Trừu tượng): ẩn chi tiết cài đặt, chỉ lộ ra interface cần thiết cho người dùng

Static member thuộc về class, không phải instance cụ thể.

  • Static variable được chia sẻ giữa tất cả instance; static method có thể gọi mà không cần tạo object.
  • Nên dùng static cho: utility functions (Math.abs()), hằng số (static final), hoặc đếm số lần instance được tạo.
  • Tránh lạm dụng static vì làm khó test và tạo hidden dependency giữa các phần của code.

NullPointerException (NPE) là runtime exception phổ biến nhất, xảy ra khi gọi method hoặc truy cập property của object null.

Cách phòng tránh:

  1. kiểm tra null trước khi dùng: if (obj != null).
  2. dùng Optional<T>: Optional.ofNullable(obj).ifPresent(...).
  3. dùng Objects.requireNonNull() để fail fast.
  4. khởi tạo biến đúng cách ngay khi khai báo để tránh null ngầm định

this tham chiếu đến object hiện tại, dùng để: tham chiếu biến/method của class hiện tại, gọi constructor của class hiện tại (this(...)), hoặc truyền object hiện tại làm tham số. super tham chiếu đến class cha, dùng để: gọi constructor của class cha (super(...)), truy cập method/biến bị override của class cha (super.method()).

Ví dụ: super.toString() gọi phiên bản của class cha.

final có ba cách dùng:

  1. final class: class không thể bị extends (ví dụ: String, Integer).
  2. final method: method không thể bị override bởi subclass.
  3. final variable: biến không thể được gán lại sau khi khởi tạo (hằng số)

Dùng final giúp tăng bảo mật, cho phép compiler tối ưu, và thể hiện rõ ý định của lập trình viên.

Abstract class có thể có constructor, state (instance variable), và cả method abstract lẫn concrete.

  • Interface chỉ định nghĩa contract — từ Java 8 trở đi interface có thêm default method và static method.
  • Một class chỉ extends một abstract class nhưng có thể implements nhiều interface.
  • Dùng abstract class khi các class con chia sẻ code và state chung; dùng interface khi định nghĩa hành vi cho các class không liên quan.

Collections Framework (giới thiệu từ JDK 1.2) cung cấp kiến trúc chuẩn để lưu trữ và thao tác nhóm đối tượng. Gồm: interfaces (Collection, List, Set, Map, Queue), implementations (ArrayList, HashMap, TreeSet), và utilities (Collections class).

Lợi ích: type-safe operations, cấu trúc dữ liệu tối ưu hiệu năng, API nhất quán cho tìm kiếm, sắp xếp, thêm, xóa.

ArrayList dùng mảng động, truy cập ngẫu nhiên O(1) nhưng thêm/xóa ở giữa O(n). LinkedList dùng doubly-linked node, truy cập ngẫu nhiên O(n) nhưng thêm/xóa ở đầu/cuối O(1).

  • Dùng ArrayList khi thường xuyên đọc; dùng LinkedList khi thường xuyên thêm/xóa ở đầu cuối. LinkedList tốn bộ nhớ hơn do node pointer.
  • Trong thực tế, ArrayList được dùng nhiều hơn vì cache-friendly.

HashSet dùng hash table, thao tác O(1) trung bình, không đảm bảo thứ tự, cho phép một phần tử null. TreeSet dùng red-black tree, thao tác O(log n), duy trì thứ tự đã sắp xếp, không cho phép null. HashSet nhanh hơn; TreeSet chậm hơn nhưng hỗ trợ range query và duyệt theo thứ tự.

Chọn HashSet khi cần hiệu năng, TreeSet khi cần duyệt theo thứ tự hoặc tìm kiếm theo range.

HashMap dùng hash table, thao tác O(1) trung bình, không đảm bảo thứ tự, cho phép null key/value, không thread-safe. TreeMap dùng red-black tree, thao tác O(log n), sắp xếp key theo thứ tự tự nhiên hoặc Comparator tùy chỉnh, không cho phép null key, không thread-safe. HashMap nhanh hơn; TreeMap hữu ích khi cần duyệt theo thứ tự hoặc tìm subMap.

HashSet implements Set, lưu trữ giá trị duy nhất — thực ra dùng HashMap bên trong (value là dummy object). HashMap implements Map, lưu cặp key-value. HashSet.add(E) trả boolean; HashMap.put(K, V) trả giá trị cũ.

  • Dùng HashSet khi chỉ cần bộ các phần tử không trùng; dùng HashMap khi cần tra cứu theo key.
  • Cả hai có hiệu năng O(1) trung bình.

Iterator là interface để duyệt qua collection mà không cần biết cấu trúc bên trong. Methods: hasNext() kiểm tra còn phần tử không, next() trả phần tử hiện tại và tiến lên, remove() xóa phần tử hiện tại.

Lợi ích: hoạt động với mọi Collection, cho phép xóa an toàn trong khi duyệt (tránh ConcurrentModificationException), tách biệt logic duyệt khỏi cấu trúc dữ liệu. Enhanced for-loop dùng Iterator ở bên dưới.

Fail-fast iterator (ArrayList, HashMap) ném ConcurrentModificationException nếu collection bị thay đổi trong khi đang duyệt. CopyOnWriteArrayList là fail-safe thật sự — iterator làm việc trên snapshot copy, không ném exception nhưng không thấy thay đổi mới. ConcurrentHashMap iterator là "weakly-consistent" — không ném exception, có thể thấy một số (nhưng không bắt buộc tất cả) thay đổi xảy ra sau khi iterator được tạo.

Phân biệt: fail-safe = snapshot (CopyOnWriteArrayList), weakly-consistent = không throw nhưng không phải snapshot (ConcurrentHashMap).

Comparable: định nghĩa thứ tự tự nhiên trong chính class đó (implement Comparable<T>, override compareTo()).

  • Mỗi class chỉ có một thứ tự so sánh. Comparator: logic so sánh bên ngoài, implement Comparator<T>, override compare().
  • Có thể tạo nhiều chiến lược sắp xếp khác nhau.
  • Dùng Comparable khi đối tượng có thứ tự rõ ràng (User theo ID); Comparator khi cần sắp xếp theo nhiều tiêu chí khác nhau (tên, tuổi, lương).

Queue là collection FIFO (First-In-First-Out) với các thao tác: offer(E) (enqueue), poll() (dequeue, trả null nếu rỗng), peek() (xem phần tử đầu không xóa).

  • Implementations: LinkedList, PriorityQueue, ArrayDeque.
  • Dùng khi: lập lịch task, xử lý message, BFS graph traversal, load balancing. PriorityQueue sắp xếp theo độ ưu tiên; Deque cho phép truy cập cả hai đầu.

Hashtable synchronized toàn bộ method — chỉ một thread truy cập tại một thời điểm, gây contention cao. ConcurrentHashMap dùng bucket-level locking (Java 8 dùng CAS + synchronized trên từng node), cho phép nhiều thread truy cập segment khác nhau đồng thời — hiệu năng tốt hơn nhiều. ConcurrentHashMap không cho phép null key/value.

Trong ứng dụng multi-threaded hiện đại, luôn ưu tiên ConcurrentHashMap; Hashtable là legacy.

Kế thừa cho phép class con kế thừa property/method từ class cha.

Các loại:

  1. Single: một class con kế thừa một class cha.
  2. Multi-level: ông → cha → con theo chuỗi.
  3. Hierarchical: một class cha, nhiều class con.
  4. Hybrid: kết hợp các loại trên (mô phỏng qua interface vì Java không hỗ trợ multiple inheritance của class)

Java không cho multiple inheritance class để tránh diamond problem, nhưng cho phép implement nhiều interface.

LSP phát biểu rằng subtype phải có thể thay thế cho parent type mà không làm hỏng behavior của chương trình. Class con nên mở rộng chức năng của class cha, không được hạn chế nó.

Ví dụ xấu: Bird có method fly(), Penguin extends Bird nhưng penguin không bay được — vi phạm LSP. Giải pháp: thiết kế hierarchy sao cho class con không yếu hóa contract của class cha. Vi phạm LSP gây lỗi runtime không mong muốn khi dùng polymorphism.

Polymorphism ("nhiều hình thức") cho phép object mang nhiều hình dạng khác nhau.

  • Compile-time (Static): method overloading — cùng tên nhưng khác tham số, compiler quyết định lúc biên dịch.
  • Runtime (Dynamic): method overriding — subclass implement khác method của parent, JVM quyết định lúc chạy dựa trên kiểu thực của object.
  • Runtime polymorphism cho phép thiết kế linh hoạt: biến reference kiểu parent có thể giữ object child, tự động gọi đúng phiên bản method.

Abstract class không thể được instantiate trực tiếp, dùng làm template với abstract methods (không có implementation) và concrete methods.

  • Subclass bắt buộc phải override abstract methods.
  • Đặc điểm: có constructor (để khởi tạo), có state (instance variable), kiểm soát access modifier tốt hơn.
  • Nên dùng khi: các class chia sẻ code/state chung, cần protected/private member, hoặc muốn non-static/non-final methods.

Interface định nghĩa contract (method signatures) mà implementations phải tuân theo.

  • Trước Java 7: chỉ có public abstract methods.
  • Java 8 thêm: default methods (concrete methods với implementation, giúp backward compatibility khi thêm method mới), static methods (utility functions).
  • Java 9+ thêm private methods (helper cho default methods).
  • Interface không có state.
  • Một class có thể implement nhiều interface.
  • Dùng cho: định nghĩa capability, loose coupling.

SOLID là 5 nguyên tắc thiết kế giúp code dễ maintain và mở rộng. (S) Single Responsibility: mỗi class chỉ có một lý do để thay đổi, một trách nhiệm duy nhất.

Ví dụ: UserRepository chỉ xử lý persistence, UserValidator chỉ xử lý validation. (O) Open/Closed: mở để mở rộng (extend), đóng để sửa đổi (modify) — mở rộng qua subclassing/composition mà không thay đổi code có sẵn. Cả hai giảm coupling và tăng testability.

Singleton giới hạn class chỉ có một instance duy nhất. Cài đặt: constructor private, static instance holder, getInstance() trả về cùng instance đó.

Lợi ích: quản lý global state, tiết kiệm tài nguyên (database connection, logger).

Nhược điểm: khó test (global state), có thể gây threading issue nếu không cẩn thận. Thread-safe versions: eager initialization, lazy với synchronized, double-checked locking, hoặc Bill Pugh initialization (dùng nested static class — khuyến nghị nhất).

Factory pattern tạo object mà không cần chỉ định class cụ thể phải instantiate. Client yêu cầu object qua factory method mà không cần biết concrete class.

Lợi ích: loose coupling, tập trung logic tạo object, dễ dàng thay implementation.

Ví dụ: Shape shape = ShapeFactory.createShape("circle") trả về Circle hoặc Square. Các biến thể: Simple Factory, Factory Method (subclass quyết định), Abstract Factory (tạo họ object liên quan).

Composition ("has-a") là bao gồm object như member thay vì extends class. Ưu tiên composition khi: quan hệ linh hoạt (không phải fundamental), cần nhiều behavior (tránh multi-inheritance), subclass chỉ override một phần của parent (vi phạm LSP).

Ví dụ: Car has-a Engine (composition) vs Car extends Vehicle (inheritance). Composition linh hoạt hơn; inheritance cho quan hệ "is-a" thực sự rõ ràng. Nguyên tắc: "favor composition over inheritance".

Method hiding xảy ra khi static method của subclass có cùng signature với static method của parent — chúng không override nhau mà che giấu. Method được gọi phụ thuộc vào kiểu reference (compile-time), không phải kiểu object thực. Ngược lại, method overriding với instance method: JVM gọi phiên bản của object thực (runtime).

Ví dụ: gọi staticMethod() qua reference kiểu parent sẽ luôn gọi phiên bản của parent dù đang giữ child object.

Thread là đơn vị thực thi nhẹ cho phép chạy concurrent.

Các cách tạo:

  1. extends Thread: class MyThread extends Thread { public void run() {...}} new MyThread().start().
  2. implements Runnable: class MyRunnable implements Runnable { public void run() {...}} new Thread(new MyRunnable()).start().
  3. Callable + ExecutorService: trả về kết quả qua Future — Future<String> f = executor.submit(() -> "result"); f.get().
  4. Virtual Threads (Java 21+): Thread.ofVirtual().start(() -> doWork()) — lightweight, không block OS thread, phù hợp I/O-heavy workload

Ưu tiên Runnable vì không block inheritance.

Luôn gọi start() thay vì run().

Synchronization ngăn data corruption khi nhiều thread truy cập tài nguyên chia sẻ. Cơ chế: keyword synchronized (method hoặc block). synchronized method: lock toàn bộ method; synchronized block: chỉ lock đoạn code cụ thể (hiệu quả hơn). Chi phí: overhead hiệu năng, có thể gây deadlock. Dùng khi: nhiều thread thay đổi shared state.

Ví dụ: synchronized void incrementCounter() { count++; } đảm bảo atomic increment. Tránh over-synchronization vì giảm parallelism.

Deadlock xảy ra khi các thread chờ đợi tài nguyên của nhau theo vòng tròn — tất cả bị blocked mãi mãi.

Ví dụ: Thread A giữ Lock 1, chờ Lock 2; Thread B giữ Lock 2, chờ Lock 1. Phòng tránh:

  1. lock ordering — luôn acquire lock theo cùng một thứ tự
  2. timeouts — dùng tryLock(timeout)
  3. tránh nested lock
  4. dùng cấu trúc cấp cao hơn (concurrent collections, ExecutorService). Phát hiện deadlock cần monitor thread states bằng JVisualVM hoặc thread dump

ExecutorService quản lý thread pool, tách biệt việc submit task khỏi cơ chế thực thi.

Lợi ích:

  1. tái sử dụng thread (giảm overhead tạo thread)
  2. số thread cố định (tránh OutOfMemoryError)
  3. task queue (xử lý burst)
  4. lifecycle management (shutdown, awaitTermination). Methods: execute(Runnable) (fire-and-forget), submit(Callable) trả Future. Tạo thread thủ công: khó quản lý, tốn kém hơn, khó test. ExecutorService là cách tiếp cận được khuyến nghị cho production code

volatile đảm bảo các lần đọc/ghi biến đều thực hiện trực tiếp trên main memory, không qua CPU cache, đảm bảo thread visibility. Thay đổi sẽ hiển thị ngay cho tất cả thread. Dùng cho: flags, initialization.

Ví dụ: volatile boolean stopFlag;. KHÔNG phù hợp cho compound operations — count++ vẫn không atomic với volatile. volatile chỉ đảm bảo visibility, không đảm bảo atomicity hay synchronization. Dùng AtomicInteger khi cần atomicity.

Future đại diện cho kết quả tính toán bất đồng bộ. submit(Callable) trả Future; gọi get() để block đến khi có kết quả. Hạn chế: blocking get(), không chain được, xử lý exception khó. CompletableFuture (Java 8+) khắc phục: non-blocking, có thể chain operations (thenApply, thenAccept, thenCompose), xử lý exception (exceptionally, handle). Java 9+ bổ sung orTimeout(), completeOnTimeout().

Ví dụ: CompletableFuture.supplyAsync(() -> fetchData()).thenApply(data -> process(data)).thenAccept(System.out::println).

Java 21+: StructuredTaskScope là alternative hiện đại cho CompletableFuture chains — quản lý vòng đời task rõ ràng hơn và hoạt động tốt với Virtual Threads.

Race condition xảy ra khi nhiều thread truy cập shared data đồng thời, kết quả cuối phụ thuộc vào thứ tự thực thi — không thể đoán trước.

Ví dụ: hai thread cùng increment count mà không synchronize dẫn đến kết quả sai. Phòng tránh:

  1. synchronization (synchronized, Lock)
  2. atomic variables (AtomicInteger)
  3. immutable objects
  4. thread-safe collections (CopyOnWriteArrayList). Nguyên nhân gốc rễ: non-atomic operations trên shared state. Test bằng cách tăng concurrency để expose lỗi

JVM (Java Virtual Machine): thực thi bytecode, platform-specific, cung cấp runtime environment và garbage collection.

  • JRE (Java Runtime Environment): JVM + libraries, chạy chương trình Java đã biên dịch, không biên dịch được.
  • JDK (Java Development Kit): JRE + compiler (javac), debugger, tools cho development.
  • Flow: JDK biên dịch .java → .class (bytecode) → JRE/JVM thực thi bytecode.
  • Developer cần JDK; end-user cần JRE; production container chỉ cần JRE.

GC tự động thu hồi bộ nhớ từ object không còn được tham chiếu.

Quá trình:

  1. Marking: duyệt object graph từ roots (stack, static references), đánh dấu object reachable.
  2. Sweeping: xóa object không được đánh dấu, giải phóng bộ nhớ.
  3. Compacting: di chuyển surviving objects lại gần nhau, giảm fragmentation

Các loại GC: Minor GC (Young Generation, nhanh), Major/Full GC (Old Generation, chậm hơn).

GC pause time ảnh hưởng trực tiếp đến latency ứng dụng — tuning GC quan trọng với hệ thống high-performance.

Generational GC chia heap thành Young và Old Generation dựa trên tuổi object.

  • Young Generation: object mới tạo, GC thường xuyên (Minor GC), nhanh.
  • Old Generation: object sống lâu, GC ít thường xuyên hơn (Major GC), chậm hơn.
  • Lý do thiết kế: hầu hết object chết trẻ ("weak generational hypothesis") — Minor GC xử lý đa số rác nhanh và hiệu quả.
  • Object sống qua nhiều Minor GC được promote lên Old Generation.
  • Tuning kích thước Young/Old ảnh hưởng GC frequency và pause time.

OutOfMemoryError xảy ra khi JVM không thể cấp phát bộ nhớ cho object mới — heap đã đầy và GC không thể giải phóng đủ.

Nguyên nhân:

  1. memory leak (object được giữ reference nhưng không dùng).
  2. data structure quá lớn.
  3. heap size không đủ. Phòng tránh:
  4. tăng heap: -Xmx2048m.
  5. fix leak (tìm và xóa reference thừa).
  6. tối ưu collection.
  7. profiling với JProfiler, VisualVM, heap dump. java.lang.OutOfMemoryError: Java heap space là loại thường gặp nhất

Memory leak trong Java: object vẫn còn reference nhưng không còn cần thiết nữa, ngăn GC thu hồi. GC không thể collect object còn được tham chiếu dù không dùng.

Ví dụ: static List list = new ArrayList(); list.add(obj); nếu list không bao giờ được clear, tất cả object tích lũy mãi. Nguyên nhân phổ biến: unclosed resources (file handle, DB connection), listener không unregister, static collection tích lũy. Phòng tránh: dùng try-with-resources, unregister listener, xóa object khỏi collection khi không dùng.

Stack overflow: quá nhiều method call (đệ quy sâu) làm cạn kiệt stack memory — StackOverflowError xảy ra nhanh và rõ ràng (stack trace hiện rõ đệ quy sâu).

  • Stack nhỏ, giới hạn per-thread.
  • Heap overflow: quá nhiều object làm cạn heap — OutOfMemoryError, GC chạy trước rồi mới throw, performance giảm trước khi crash.
  • Heap overflow khó diagnose hơn, cần profiling/heap dump.
  • Phòng tránh stack overflow: tránh đệ quy sâu, dùng iteration; phòng heap overflow: quản lý object creation, fix memory leak.

ClassLoader tải file .class vào memory lúc runtime.

Ba loại:

  1. Bootstrap ClassLoader: JVM built-in, tải core Java classes (java.lang, java.util).
  2. Platform ClassLoader (trước Java 9 gọi là Extension ClassLoader): tải classes từ thư mục JRE extensions.
  3. Application ClassLoader: tải application classpath

Hierarchy: Application → Extension → Bootstrap (parent-delegation model — con hỏi cha trước).

Custom ClassLoader cho phép dynamic loading, hot-reloading.

Hiểu ClassLoader quan trọng để debug JAR conflict, OSGi frameworks, application server issues.

Spring Boot đơn giản hóa phát triển ứng dụng Spring qua auto-configuration, embedded server, và standalone JAR.

Ưu điểm:

  1. giảm boilerplate — không cần XML config
  2. embedded Tomcat/Jetty — không cần deploy war
  3. production-ready defaults
  4. dependency management dễ dàng (spring-boot-starters)
  5. monitoring tích hợp (Actuator). Convention-over-configuration giúp phát triển nhanh. Lý tưởng cho microservices, REST APIs, cloud deployment

DI cung cấp dependencies cho object từ bên ngoài thay vì object tự tạo — giảm coupling, tăng testability.

Spring cài đặt qua:

  1. Constructor Injection: public UserService(UserRepository repo) {} — được khuyến nghị nhất, immutable, dependencies rõ ràng.
  2. Setter Injection: setRepository(UserRepository) — cho optional dependencies.
  3. Field Injection: @Autowired UserRepository — đơn giản nhưng khó test

IoC container quản lý instantiation và wiring.

Constructor Injection là best practice vì enforce required dependencies.

Tất cả đều là stereotype annotation kích hoạt component scanning và tạo Spring bean. @Component: generic, đánh dấu class là Spring bean. @Controller: chuyên biệt cho web controller xử lý HTTP request. @Service: chuyên biệt cho business logic layer. @Repository: chuyên biệt cho data access, tự động dịch database exception sang Spring DataAccessException.

Về mặt kỹ thuật tương đương nhau, nhưng sử dụng đúng annotation giúp code tự tài liệu hóa và rõ ý định thiết kế.

@Autowired kích hoạt tự động inject dependency — Spring tự resolve và inject.

  • Có thể dùng trên: constructor, setter, field.
  • Mặc định: wire by type (theo kiểu).
  • Nếu có nhiều bean cùng kiểu, dùng @Qualifier("name") để chỉ định.
  • Dependencies optional: @Autowired(required=false) không throw lỗi nếu không có bean.
  • Khuyến nghị: Constructor injection (rõ dependencies, dễ test); tránh Field injection (hidden dependencies, khó mock trong unit test).

@Bean: đặt trên method trong @Configuration class, method trả về object được thêm vào container.

  • Kiểm soát tường minh quá trình tạo bean (logic khởi tạo phức tạp). @Component: đặt trên class, Spring tự instantiate qua default constructor.
  • Ưu tiên @Bean cho: thư viện bên thứ ba (không sửa được source), khởi tạo phức tạp, conditional bean.
  • Ưu tiên @Component cho: class của ứng dụng, auto-detection đơn giản.
  • Cả hai tạo singleton bean theo mặc định.

Các file config externalize settings ra ngoài code — chứa giá trị environment-specific (DB URL, port, log level). application.properties: format key=value. application.yml: hierarchy theo indentation, dễ đọc hơn với cấu trúc lồng nhau. Spring tải properties vào Environment, truy cập qua @Value("${key}") hoặc @ConfigurationProperties.

Lợi ích: deploy cùng JAR qua nhiều environment (dev, test, prod) với config khác nhau. Profiles: application-dev.yml cho environment-specific config.

Actuator cung cấp production-ready endpoints để expose thông tin nội bộ ứng dụng qua HTTP/JMX.

  • Các endpoint chính: /health (trạng thái ứng dụng), /metrics (performance metrics), /env (configuration), /beans (registered beans), /loggers (log levels).
  • Không cần viết custom code để monitoring.
  • Kích hoạt bằng dependency spring-boot-starter-actuator, configure expose endpoint trong properties.
  • Quan trọng cho: operational visibility, debugging, cloud-native (Kubernetes health check).

Starters là dependency descriptor đơn giản hóa cấu hình Maven/Gradle. Thay vì liệt kê hàng chục dependency với version riêng, spring-boot-starter-web bao gồm tất cả: Spring MVC, Tomcat, Jackson, validation — các version đã được test tương thích với nhau.

Lợi ích:

  1. version consistency — không lo conflict version
  2. giảm boilerplate
  3. convention-over-configuration
  4. setup nhanh. Các starter phổ biến: spring-boot-starter-data-jpa, spring-boot-starter-security, spring-boot-starter-test

Spring Boot nhúng application server (Tomcat, Jetty, Undertow) trực tiếp vào JAR — không cần deploy lên server ngoài.

Lợi ích:

  1. một artifact deploy duy nhất
  2. giảm phức tạp operations
  3. environment consistency (dev và prod cùng runtime)
  4. container-friendly (Docker, Kubernetes). Chạy đơn giản bằng: java -jar app.jar. Port cấu hình qua server.port. Đánh đổi: startup chậm hơn deployment truyền thống, tốn memory hơn (mỗi JAR có Tomcat riêng). Hoàn hảo cho microservices

Spring Data JPA cung cấp abstraction trên JPA (Java Persistence API), giảm boilerplate drastically. @Repository: annotation đánh dấu data access object, kích hoạt exception translation. Implement CrudRepository<Entity, ID>: tự động có CRUD methods (save, findById, findAll, delete) mà không cần viết code. Custom query: @Query("SELECT u FROM User u WHERE u.email = :email"). Pagination/sorting qua PagingAndSortingRepository.

Lợi ích: minimal code, type-safe queries, transaction management tự động.

Virtual Threads (Project Loom, Java 21) là các luồng nhẹ do JVM quản lý, cho phép chạy hàng triệu luồng đồng thời trên chỉ vài OS thread. Khác với Platform Thread (1:1 với OS thread), Virtual Thread dùng lịch trình M:N — nhiều virtual thread trên ít OS thread.

Lợi ích: viết code đồng bộ đơn giản nhưng đạt được concurrency cao (xử lý 10K+ request) mà không cần async/reactive phức tạp. Cách dùng: Thread.ofVirtual().start(() -> doWork()) hoặc Executors.newVirtualThreadPerTaskExecutor(). Lý tưởng cho: web server, microservices, I/O-bound workloads. Quan trọng: không thích hợp cho CPU-bound tasks (không giúp gì nếu bottleneck là tính toán, không phải I/O).

Record (preview từ Java 14, chính thức từ Java 16) là loại class đặc biệt dành cho dữ liệu thuần túy — immutable data carrier.

Khai báo: record Person(String name, int age) {}.

Compiler tự sinh: constructor, getters (name(), age()), equals(), hashCode(), toString().

So với POJO thông thường: ít boilerplate hơn rất nhiều.

Khi nên dùng:

  1. DTO (Data Transfer Objects) — dữ liệu giữa các layer.
  2. Value objects.
  3. API response/request model.
  4. khi cần immutability rõ ràng

Record Patterns (Java 21): deconstruct trực tiếp trong switch/instanceof — if (obj instanceof Person(String name, int age)) { ... }.

Hạn chế: không extend class (chỉ implement interface), không thêm mutable field.

Sealed Class (Java 17+) hạn chế những class nào được phép extend nó — khai báo qua permits.

Ví dụ: public sealed class Shape permits Circle, Rectangle, Triangle {}. Mỗi subclass phải là final, sealed, hoặc non-sealed. Lợi ích kết hợp Pattern Matching: compiler biết tất cả subtypes nên có thể kiểm tra exhaustiveness — switch expression không cần default nếu đã xử lý hết.

Ví dụ: switch (shape) { case Circle c -> ...; case Rectangle r -> ...; case Triangle t -> ...; } — compiler báo lỗi nếu bỏ sót một case. Tốt hơn abstract class ở điểm: đóng gói hierarchy chặt hơn, phòng tránh subtype ngoài ý muốn. Ứng dụng: domain model, error type, state machine.

Mono<T>: publisher phát ra 0 hoặc 1 phần tử — tương tự Optional nhưng async. Flux<T>: publisher phát ra 0 đến N phần tử — tương tự Stream nhưng async và có thể vô tận. Cả hai là lazy — chỉ chạy khi có subscriber. Backpressure: cơ chế consumer báo cho producer "tôi chỉ nhận được X phần tử/giây" — ngăn producer overwhelm consumer. Quan trọng khi xử lý dữ liệu lớn: stream 1 triệu record từ DB mà không backpressure sẽ crash memory.

Ví dụ: Flux.fromStream(bigQuery).onBackpressureBuffer(1000). Kết hợp WebClient: gọi API bên ngoài không block thread trong khi chờ response. Thao tác chính: map(), flatMap(), filter(), zip(), merge() — chain các async operation một cách declarative.

Testcontainers khởi động Docker container thật (Postgres, Kafka, Redis, MongoDB) trong test — không dùng H2 in-memory hay mock.

Lợi ích: test chạy trên schema thật, SQL syntax thật, migration thật — bắt được lỗi mà mock không phát hiện (index, FK constraint, trigger). Cách dùng Spring Boot: @SpringBootTest + @Testcontainers annotation, define container là @Container static. Spring Boot 3.1+ hỗ trợ ServiceConnection — tự override datasource URL, không cần config thủ công. Chi phí: chậm hơn unit test (~5-10s khởi động container) nhưng nhanh hơn nhiều so với deploy lên test server. Best practice: dùng cho DAO layer, event publishing, message queue integration. Tái sử dụng container qua @Container static — khởi động 1 lần cho cả test class.