Topic: Common
Junior Level (0–2 years)
🔹 Core Java
What is immutable object?
Answer
🔹 Immutable object là gì?
- Immutable object (đối tượng bất biến) là đối tượng mà trạng thái (giá trị các thuộc tính) của nó không thể thay đổi sau khi được tạo ra.
- Tức là, một khi bạn đã khởi tạo object, thì tất cả field của nó sẽ luôn giữ nguyên đến cuối vòng đời.
🔹 Đặc điểm
- Không thể thay đổi sau khi tạo
- Mọi method “thay đổi” dữ liệu thực chất sẽ tạo ra một object mới thay vì thay đổi object hiện tại.
- Thread-safe tự nhiên
- Vì không thay đổi được, nên có thể chia sẻ giữa nhiều thread mà không sợ bị race condition.
- So sánh dễ dàng
- Do giá trị không đổi, nên kết quả của
hashCode()vàequals()luôn ổn định. - Thường được dùng làm key trong HashMap hoặc phần tử trong HashSet.
- Do giá trị không đổi, nên kết quả của
🔹 Ví dụ trong Java
Stringlà class immutable: javaCopyEditString s1 = "Hello"; String s2 = s1.concat(" World"); System.out.println(s1); // "Hello" (không đổi) System.out.println(s2); // "Hello World" (object mới)➝concat()không thay đổis1mà trả về object mới.- Wrapper classes (
Integer,Double,Boolean, …) cũng là immutable.
🔹 Cách tạo immutable object
- Khai báo class là
final(không cho kế thừa). - Các field là
privatevàfinal. - Không cung cấp setter.
- Nếu có field là object khác, thì cần clone hoặc copy để tránh bị thay đổi từ bên ngoài.
Ví dụ:
javaCopyEditpublic final class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
🔹 Ưu và nhược điểm
✅ Ưu điểm:
- An toàn trong môi trường đa luồng.
- Dễ debug, dễ test.
- Có thể cache hoặc dùng lại mà không lo thay đổi dữ liệu.
❌ Nhược điểm:
Mỗi lần “thay đổi” là phải tạo object mới → có thể tốn bộ nhớ hơn trong một số trường hợp (ví dụ xử lý chuỗi lớn).
What are the differencces between == and .equals() ?
Answer
🔹 .equals() Method
- Purpose: Compares references (for objects) or values (for primitives).
- Primitives (
int,double,char, etc.):==compares the actual values.
int a = 5, b = 5; System.out.println(a == b); // true
- Objects:
==checks if both references point to the same memory location (i.e., the same object).
String s1 = new String("hello"); String s2 = new String("hello"); System.out.println(s1 == s2); // false (different objects in memory)
🔹 .equals() Method
- Purpose: Compares the contents/values of objects.
- Defined in the
Objectclass, but often overridden (e.g., inString,Integer,List, etc.). - Default implementation in
Objectbehaves like==(compares references). - Example with
String:
String s1 = new String("hello"); String s2 = new String("hello"); System.out.println(s1.equals(s2)); // true (same content)
🔹 Key Differences
| Aspect | == | .equals() |
|---|---|---|
| Primitives | Compares actual values | Not applicable (primitives don’t have methods) |
| Objects | Compares references (same memory address) | Compares contents/values (if overridden) |
| Default behavior | Always reference check | Reference check (unless overridden) |
| Overridable? | ❌ No (operator fixed) | ✅ Yes (custom classes can override) |
What are HashMap and Hashtable? Differences?
Answer
🔹 What is a HashMap?
- Introduced in Java 1.2 (part of Collections Framework).
- Stores data as key–value pairs.
- Uses hashing internally:
hashCode()→ find the bucketequals()→ check if key already exists.
- Not synchronized → better performance in single-threaded apps.
- Allows 1 null key and multiple null values.
🔹 What is a Hashtable?
- Introduced in Java 1.0 (legacy class, before Collections Framework).
- Also stores key–value pairs using hashing.
- Synchronized → thread-safe but slower.
- Does NOT allow
nullkeys ornullvalues. - Largely replaced by
HashMap+Collections.synchronizedMap()orConcurrentHashMap.
🔹 Key Differences
| Feature | HashMap | Hashtable |
|---|---|---|
| Package | Java 1.2, part of Collections Framework | Java 1.0, legacy class |
| Synchronization | ❌ Not synchronized (faster) | ✅ Synchronized (thread-safe, slower) |
| Null keys/values | 1 null key, multiple null values allowed | ❌ No null keys or null values allowed |
| Iteration | Iterator (fail-fast → ConcurrentModificationException) | Enumerator (legacy, not fail-fast) |
| Performance | Faster in single-threaded apps | Slower due to synchronization overhead |
| Preferred in new code | ✅ Yes | ❌ No (use ConcurrentHashMap instead) |
Explain the concept of OOP: Encapsulation, Inheritance, Polymorphism, Abstraction.
Answer
What is a final variable/class/method?
Answer
🔹 1. final Variable
- A
finalvariable’s value cannot be changed once assigned. - It makes the variable a constant (if primitive) or a constant reference (if object reference).
Examples:
javaCopyEditfinal int x = 10;
// x = 20; // ❌ Compile error: cannot assign a value to final variable
final StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // ✅ allowed (object can mutate)
sb = new StringBuilder("New"); // ❌ not allowed (reference cannot change)
👉 Meaning: final locks the reference, not necessarily the contents.
🔹 2. final Method
- A
finalmethod cannot be overridden by subclasses. - Useful when you want to fix a behavior and prevent subclasses from changing it.
Example:
javaCopyEditclass Parent {
public final void display() {
System.out.println("This is final method");
}
}
class Child extends Parent {
// ❌ Compile error: cannot override final method
// public void display() { ... }
}
🔹 3. final Class
- A
finalclass cannot be inherited (extended). - Ensures immutability or security (nobody can subclass and alter its behavior).
Example:
javaCopyEditfinal class Utility {
public static void log(String msg) {
System.out.println(msg);
}
}
// ❌ Compile error: cannot subclass final class
// class ExtendedUtility extends Utility { }
- Real-world:
java.lang.Stringisfinal→ ensures immutability and security.
🔹 Quick Summary
| Usage | Effect |
|---|---|
final variable | Value (or reference) cannot be reassigned |
final method | Method cannot be overridden |
final class | Class cannot be extended |
✅ Mnemonic:
Class → no inheritance
Variable → constant
Method → locked behavior
What is the difference between ArrayList and LinkedList?
Answer
🔹 1. Cấu trúc lưu trữ
- ArrayList
- Dựa trên mảng động (
dynamic array). - Các phần tử được lưu liên tiếp trong bộ nhớ.
- Khi mảng đầy, nó sẽ tự động cấp phát mảng mới (thường gấp đôi kích thước) rồi copy dữ liệu sang.
- Dựa trên mảng động (
- LinkedList
- Dựa trên danh sách liên kết kép (
doubly linked list). - Mỗi phần tử (node) chứa:
- Giá trị (
data) - Con trỏ đến node trước (
prev) - Con trỏ đến node sau (
next)
- Giá trị (
- Các phần tử không cần nằm liên tiếp trong bộ nhớ.
- Dựa trên danh sách liên kết kép (
🔹 2. Truy cập phần tử (Access)
- ArrayList:
O(1)để truy cập phần tử theo index (random access).- Rất nhanh khi bạn hay dùng
get(i).
- LinkedList:
O(n)để truy cập phần tử theo index, vì phải duyệt tuần tự từ đầu hoặc cuối list.- Thích hợp hơn cho duyệt tuần tự chứ không phải random access.
🔹 3. Thêm / Xóa phần tử (Insert / Delete)
- ArrayList:
- Thêm vào cuối:
O(1)(trung bình). - Thêm / xóa ở giữa hoặc đầu:
O(n)vì cần dịch chuyển (shift) các phần tử sau đó.
- Thêm vào cuối:
- LinkedList:
- Thêm / xóa ở đầu hoặc cuối:
O(1). - Thêm / xóa ở giữa: cần duyệt đến vị trí trước →
O(n)để tìm, nhưng khi đã có con trỏ node thì chèn/xóa làO(1).
- Thêm / xóa ở đầu hoặc cuối:
🔹 4. Dung lượng bộ nhớ (Memory)
- ArrayList:
- Tốn ít bộ nhớ hơn, chỉ lưu giá trị.
- Nhưng có thể lãng phí dung lượng mảng đã cấp phát mà chưa dùng.
- LinkedList:
- Tốn nhiều bộ nhớ hơn do mỗi node phải lưu thêm 2 con trỏ (
prev,next).
- Tốn nhiều bộ nhớ hơn do mỗi node phải lưu thêm 2 con trỏ (
🔹 5. Khi nào dùng cái nào?
- Dùng ArrayList khi:
- Cần truy cập nhanh theo index.
- Các thao tác thêm / xóa chủ yếu ở cuối list.
- Muốn tiết kiệm bộ nhớ.
- Dùng LinkedList khi:
- Thêm / xóa thường xuyên ở đầu hoặc giữa danh sách.
- Không quan trọng truy cập ngẫu nhiên bằng index.
- Muốn tránh cost copy dữ liệu khi mảng bị resize (như trong ArrayList).
🔹 6. Tóm tắt (So sánh nhanh)
| Tiêu chí | ArrayList | LinkedList |
|---|---|---|
| Cấu trúc | Mảng động | Danh sách liên kết kép |
| Truy cập (get/set) | O(1) | O(n) |
| Thêm cuối (add) | O(1) trung bình | O(1) |
| Thêm đầu/giữa | O(n) | O(1) sau khi tìm node |
| Xóa cuối | O(1) | O(1) |
| Xóa đầu/giữa | O(n) | O(1) sau khi tìm node |
| Bộ nhớ | Ít overhead hơn | Tốn thêm con trỏ prev/next |
🔹 Một số ví dụ thực tế
- ArrayList
- Danh sách sinh viên, sản phẩm → cần truy cập ngẫu nhiên nhanh.
- Lưu cache tạm (read nhiều, update ít).
- Danh sách log/record thêm cuối liên tục.
- LinkedList
- Cài đặt Queue/Stack:
offer(),poll(),push(),pop(). - Xử lý lịch sử (Undo/Redo): thêm/xóa đầu nhanh.
- Trình duyệt duyệt trang trước/sau (prev/next navigation).
- Cài đặt Queue/Stack:
🔹 Kinh nghiệm chọn nhanh
- Nếu 90% thao tác là đọc/truy cập theo index → ArrayList.
- Nếu 90% thao tác là chèn/xóa nhiều ở đầu hoặc giữa → LinkedList.
- Còn nếu cần cấu trúc hàng đợi (FIFO/LIFO) → hay dùng LinkedList hoặc
ArrayDeque.
What is method overloading vs method overriding?
Answer
🔹 Method Overloading
Definition:
- Same method name, but different parameter lists (different number or type of arguments) within the same class.
- Happens at compile-time → also called compile-time polymorphism.
- Return type may be different, but parameter list must be different.
Example (Overloading):
javaCopyEditclass Calculator {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
}
Calculator calc = new Calculator();
System.out.println(calc.add(2, 3)); // calls add(int,int)
System.out.println(calc.add(2.5, 3.5)); // calls add(double,double)
System.out.println(calc.add(1, 2, 3)); // calls add(int,int,int)
🔹 Method Overriding
Definition:
- Subclass provides a specific implementation of a method that is already defined in its superclass.
- Same method name, same parameters, same return type (or covariant return type).
- Happens at runtime → also called runtime polymorphism.
- Requires inheritance.
Example (Overriding):
javaCopyEditclass Animal {
void sound() {
System.out.println("Some generic sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Bark");
}
}
Animal a = new Dog();
a.sound(); // Bark (runtime decision)
🔹 Key Differences
| Aspect | Overloading | Overriding |
|---|---|---|
| Definition | Same name, different parameters | Same name, same parameters, subclass redefines |
| Return type | Can be different (if params differ) | Must be same (or covariant) |
| Occurs in | Same class (or subclass too) | Subclass only (inheritance required) |
| Polymorphism type | Compile-time polymorphism | Runtime polymorphism |
| Access modifiers | No restriction | Cannot reduce visibility (e.g. public → protected not allowed) |
| Annotations | @Override not applicable | @Override applicable |
✅ In short:
Overriding = subclass changes behavior of parent method (runtime).
Overloading = same method name, different parameters (compile-time).
Explain basic access modifiers: public, private, protected, default.
Answer
| Modifier | Same Class | Same Package | Subclass (diff package) | Other Packages |
|---|---|---|---|---|
| public | ✅ | ✅ | ✅ | ✅ |
| protected | ✅ | ✅ | ✅ | ❌ |
| default | ✅ | ✅ | ❌ | ❌ |
| private | ✅ | ❌ | ❌ | ❌ |
🔹 Exception Handling
Difference between checked and unchecked exceptions?
Answer
🔹 Checked Exceptions (compile-time)
Must be either handled (
try/catch) or declared (throws).
I/O & File Handling
IOExceptionFileNotFoundExceptionEOFExceptionInterruptedIOException
SQL & Database
SQLExceptionSQLTimeoutException
Class Loading & Reflection
ClassNotFoundExceptionInstantiationExceptionIllegalAccessExceptionInvocationTargetExceptionNoSuchMethodException
Concurrency
InterruptedExceptionExecutionException
Other Common
CloneNotSupportedExceptionTimeoutException
🔹 Unchecked Exceptions (runtime)
Extend
RuntimeException, compiler does not force handling.
Null & Illegal State
NullPointerExceptionIllegalStateExceptionUnsupportedOperationException
Arithmetic & Numbers
ArithmeticExceptionNumberFormatExceptionInputMismatchException
Array & Collections
ArrayIndexOutOfBoundsExceptionStringIndexOutOfBoundsExceptionIndexOutOfBoundsExceptionConcurrentModificationExceptionNoSuchElementException
Casting & Class Issues
IllegalArgumentException
ClassCastException
How does try-catch-finally work?
Answer
🔹 Java Basics
What is the purpose of static?
Answer
What are constructors? Can a constructor be private?
Answer
Explain the lifecycle of a Java object.
Answer
🔹 1. Khởi tạo (Creation)
- Khi bạn dùng
newhoặc reflection (Class.newInstance(),Constructor.newInstance()) → JVM sẽ:- Cấp phát bộ nhớ trên Heap cho object.
- Gọi constructor để khởi tạo giá trị ban đầu.
- Gán reference (biến tham chiếu) trỏ đến object đó.
Ví dụ:
javaCopyEditPerson p = new Person("Alice"); // Object được tạo trong heap
🔹 2. Sử dụng (In Use / Live)
- Object được chương trình sử dụng thông qua các reference.
- Trong giai đoạn này, bạn có thể gọi method, thay đổi state (nếu không immutable).
Ví dụ:
javaCopyEditp.setAge(25);
System.out.println(p.getName());
🔹 3. Không còn tham chiếu (Unreachable)
- Khi không còn biến nào trỏ đến object, nó trở thành unreachable.
- Object lúc này không thể truy cập được nữa, và chờ để Garbage Collector (GC) dọn dẹp.
Ví dụ:
javaCopyEditp = null; // object "Alice" không còn tham chiếu
🔹 4. Dọn dẹp bởi Garbage Collector (Garbage Collection)
- JVM chạy Garbage Collector để giải phóng bộ nhớ.
- Trước khi bị huỷ, nếu class có override
finalize()(trước Java 9, nay deprecated) thì method đó được gọi một lần duy nhất.
Ví dụ (không khuyến khích dùng finalize):
javaCopyEdit@Override
protected void finalize() throws Throwable {
System.out.println("Object is being garbage collected");
}
🔹 5. Huỷ (Destroyed)
- Sau khi GC dọn dẹp, bộ nhớ được giải phóng.
- Object biến mất hoàn toàn khỏi Heap.
🔹 Tóm tắt Vòng đời của Object
Huỷ (Destroyed) → Object bị xoá khỏi heap.
Tạo (Creation) → new → JVM cấp phát heap + gọi constructor.
Sống (In Use) → Object được dùng qua reference.
Mất tham chiếu (Unreachable) → không còn biến nào trỏ đến.
GC dọn dẹp (Garbage Collection) → JVM thu hồi bộ nhớ.
🔹 String & Collections
Why are Strings immutable?
Answer
🔹 Summary
Why are Strings immutable?
Reliable, bug-free APIs (no unexpected modifications).
Security (sensitive data protection).
String Pool efficiency (safe reuse of literals).
Thread-safety (no need for synchronization).
Hashing performance (stable hashCode).
How does HashMap work internally?
Answer
What is the difference between List, Set, and Map?
Answer
🔹 1. List
- Đặc điểm:
- Dữ liệu có thứ tự (ordered).
- Cho phép phần tử trùng lặp.
- Có thể truy cập bằng chỉ số (index).
- Các implementation phổ biến:
ArrayList,LinkedList,Vector,Stack. - Ví dụ:
javaCopyEditList<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("A"); // cho phép trùng lặp
System.out.println(list); // [A, B, A]
🔹 2. Set
- Đặc điểm:
- Không cho phép phần tử trùng lặp.
- Không đảm bảo thứ tự (trừ một số loại như
LinkedHashSet,TreeSet).
- Các implementation phổ biến:
HashSet→ không thứ tự.LinkedHashSet→ giữ thứ tự chèn vào.TreeSet→ sắp xếp theo tự nhiên hoặc comparator.
- Ví dụ:
javaCopyEditSet<String> set = new HashSet<>();
set.add("A");
set.add("B");
set.add("A"); // bị bỏ qua
System.out.println(set); // [A, B] (thứ tự không đảm bảo)
🔹 3. Map
- Đặc điểm:
- Lưu dữ liệu theo cặp key–value.
- Key phải duy nhất, nhưng value có thể trùng lặp.
- Không kế thừa
Collection, mà là một interface riêng.
- Các implementation phổ biến:
HashMap→ nhanh, không đảm bảo thứ tự.LinkedHashMap→ giữ thứ tự chèn.TreeMap→ sắp xếp theo key.
- Ví dụ:
javaCopyEditMap<Integer, String> map = new HashMap<>();
map.put(1, "Alice");
map.put(2, "Bob");
map.put(1, "Charlie"); // ghi đè value cho key=1
System.out.println(map); // {1=Charlie, 2=Bob}
🔹 Bảng so sánh nhanh
| Collection | Thứ tự | Cho phép trùng lặp | Truy cập theo index | Dữ liệu lưu dạng |
|---|---|---|---|---|
| List | Có | ✅ Có | ✅ Có | Phần tử (element) |
| Set | Không (hoặc có tuỳ loại) | ❌ Không | ❌ Không | Phần tử (element) |
| Map | Không (hoặc có tuỳ loại) | ❌ Key không, ✅ Value có | ❌ Không | Cặp (key → value) |
✅ Tóm lại:
Map → khi cần ánh xạ key → value.
List → khi cần danh sách có thứ tự, cho phép trùng.
Set → khi cần tập hợp không trùng lặp.
When do you use ArrayList vs LinkedList?
Answer
Mid-Level (2–5 years)
🔹 Java Features
What are Streams and how do you use them?
Answer
🔹 1. Streams là gì?
Streamtrong Java (từ Java 8) là một API dùng để xử lý tập hợp dữ liệu (collections, arrays, I/O) theo cách khai báo (declarative).- Nó cho phép lập trình functional style (giống như SQL, hoặc map/filter trong các ngôn ngữ functional).
- Streams ≠ Collections:
- Collection lưu trữ dữ liệu.
- Stream chỉ là dòng dữ liệu (pipeline) được tạo từ collection/array và xử lý tuần tự hoặc song song.
🔹 2. Đặc điểm chính
- Không lưu trữ dữ liệu → chỉ xử lý dữ liệu có sẵn.
- Lười thực thi (lazy) → chỉ chạy khi có thao tác “terminal” (như
collect(),forEach()). - Có thể tuần tự hoặc song song (
parallelStream()). - Hỗ trợ pipeline: kết hợp nhiều operation liên tiếp (map, filter, sort, reduce…).
🔹 3. Cách sử dụng Streams
Quy trình 3 bước:
- Tạo stream từ collection/array.
List<String> list = Arrays.asList("Java", "Python", "C++");
Stream<String> stream = list.stream();
- Trung gian (Intermediate operations): xử lý, biến đổi (không thực thi ngay).
map()→ biến đổi dữ liệufilter()→ lọc dữ liệusorted()→ sắp xếpdistinct()→ loại trùng
- Kết thúc (Terminal operations): kích hoạt xử lý stream.
collect()→ gom kết quả thành list/set/mapforEach()→ duyệt và in rareduce()→ gộp dữ liệu thành 1 giá trị
Intermediate: filter, map, flatMap, distinct, sorted, peek, limit, skip, mapToInt/Long/Double.
Terminal: forEach, forEachOrdered, toArray, reduce, collect, min, max, count, anyMatch, allMatch, noneMatch, findFirst, findAny.
🔹 5. Streams tuần tự vs song song
- Sequential Stream: xử lý lần lượt (mặc định).
- Parallel Stream: xử lý song song (đa luồng) → tăng tốc với dữ liệu lớn.
list.parallelStream()
.forEach(System.out::println);
How does Optional help avoid NullPointerException?
Answer
Optionalgiúp thay thế null bằng cơ chế an toàn.
- Bắt buộc developer phải xử lý rõ ràng trường hợp không có giá trị.
- Cung cấp nhiều API tiện lợi (
map,orElse,ifPresent, …) để viết code ngắn gọn, sạch sẽ. - Nhờ vậy, giảm thiểu tối đa lỗi NullPointerException.
What is the difference between synchronized and Lock?
Answer
🔹 1. synchronized
- Là keyword (từ khóa) có sẵn của Java.
- Dùng để đảm bảo chỉ một thread được truy cập vào block hoặc method cùng lúc.
- JVM tự động quản lý lock và release lock.
Ví dụ:
javaCopyEditpublic synchronized void increment() {
count++;
}
Hoặc:
javaCopyEditsynchronized (this) {
count++;
}
👉 Ưu điểm: đơn giản, dễ dùng.
👉 Nhược điểm:
- Ít linh hoạt.
- Không thể kiểm soát lock chi tiết (không biết có lock được hay không, không có tryLock, không có timeout).
- Khi một thread bị block thì phải chờ đến khi lock được giải phóng.
🔹 2. Lock (trong java.util.concurrent.locks)
Locklà một interface (Java 5+) cho phép kiểm soát chi tiết hơn việc khóa.- Cần explicit lock/unlock (người lập trình phải gọi).
Ví dụ:
javaCopyEditLock lock = new ReentrantLock();
lock.lock();
try {
count++;
} finally {
lock.unlock(); // phải nhớ giải phóng lock
}
👉 Ưu điểm:
- Linh hoạt hơn
synchronized. - Hỗ trợ tryLock() → thử lấy lock mà không chờ vô hạn.
- Hỗ trợ lockInterruptibly() → thread có thể bị interrupt khi đang chờ lock.
- Hỗ trợ fair lock (công bằng, FIFO).
- Có thể tạo nhiều điều kiện chờ với
Condition(tương tựwait/notify, nhưng mạnh hơn).
👉 Nhược điểm:
- Lập trình viên phải tự quản lý
lock.unlock()(nếu quên → deadlock). - Code phức tạp hơn.
🔹 3. Bảng so sánh nhanh
| Tiêu chí | synchronized | Lock (ReentrantLock v.v.) |
|---|---|---|
| Bản chất | Từ khóa (keyword) của Java | Interface trong java.util.concurrent |
| Cơ chế unlock | JVM tự động release khi ra khỏi block | Phải gọi unlock() thủ công (trong finally) |
| Công bằng (fairness) | ❌ Không hỗ trợ | ✅ Hỗ trợ fair lock |
| Thử lấy lock | ❌ Không | ✅ tryLock() |
| Timeout khi chờ lock | ❌ Không | ✅ Có (tryLock với timeout) |
| Interrupt khi chờ lock | ❌ Không | ✅ lockInterruptibly() |
| Condition (chờ & thông báo) | Chỉ có wait/notify/notifyAll | Có Condition linh hoạt hơn |
| Đơn giản / dễ dùng | ✅ Rất dễ dùng | ❌ Phức tạp hơn, dễ quên unlock |
Explain Functional Interfaces and Lambdas.
Answer
🔹 1. Functional Interface là gì?
- Là interface chỉ có duy nhất 1 method trừu tượng (abstract method) → gọi là SAM (Single Abstract Method).
- Có thể có thêm default method hoặc static method, nhưng chỉ được phép có 1 abstract method.
- Thường được đánh dấu bằng annotation
@FunctionalInterface(không bắt buộc, nhưng nên dùng để compiler kiểm tra).
Ví dụ:
javaCopyEdit@FunctionalInterface
interface MyFunction {
int apply(int x, int y); // chỉ 1 abstract method
}
👉 Một số functional interface có sẵn trong java.util.function:
Predicate<T>→ nhậnT, trả vềboolean.Function<T, R>→ nhậnT, trả vềR.Consumer<T>→ nhậnT, không trả về gì (void).Supplier<T>→ không nhận gì, trả vềT.UnaryOperator<T>→ nhậnT, trả vềT.BinaryOperator<T>→ nhận 2T, trả vềT.
🔹 2. Lambda Expression là gì?
- Lambda là cú pháp rút gọn để triển khai (implement) một functional interface.
- Giúp viết code ngắn gọn, rõ ràng hơn thay vì phải dùng anonymous class.
Cú pháp:
javaCopyEdit(parameters) -> expression
(parameters) -> { statements }
🔹 3. Ví dụ minh họa
Truyền thống (Anonymous class):
javaCopyEditMyFunction add = new MyFunction() {
@Override
public int apply(int x, int y) {
return x + y;
}
};
System.out.println(add.apply(2, 3)); // 5
Với Lambda:
javaCopyEditMyFunction add = (x, y) -> x + y;
System.out.println(add.apply(2, 3)); // 5
👉 Rút gọn rất nhiều so với cách cũ.
🔹 4. Dùng Lambda với Functional Interface có sẵn
javaCopyEditList<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// Consumer<T>
names.forEach(name -> System.out.println(name));
// Predicate<T>
names.stream()
.filter(name -> name.startsWith("A"))
.forEach(System.out::println);
// Function<T, R>
List<Integer> lengths = names.stream()
.map(s -> s.length())
.toList();
🔹 5. Lợi ích
- Code ngắn gọn, dễ đọc.
- Hỗ trợ lập trình hàm (functional style).
- Kết hợp mạnh mẽ với Streams API.
- Giảm bớt boilerplate code khi dùng anonymous class.
🔹 6. Tóm lại
Kết hợp lại giúp Java hỗ trợ functional programming (Streams, xử lý dữ liệu theo pipeline).
Functional Interface = interface có 1 abstract method.
Lambda = cú pháp rút gọn để implement method đó.
🔹 Multithreading
How does ExecutorService work?
Answer
🔹 1. ExecutorService là gì?
- Là một interface trong package
java.util.concurrent. - Nó quản lý và điều phối một pool (nhóm) thread thay vì bạn phải tự tạo và quản lý từng thread bằng tay (
new Thread(...).start()). - Bạn chỉ cần gửi task (Runnable/Callable) → ExecutorService sẽ tự phân công cho thread thích hợp để chạy.
👉 Giúp tách biệt:
- Người lập trình chỉ quan tâm đến nhiệm vụ (task).
- Còn ExecutorService lo phần thread management.
🔹 2. Các thành phần chính
- Task:
Runnable→ chạy nhưng không trả về giá trị.Callable<T>→ chạy và trả về giá trị kiểuT, có thể ném exception.
- Future<T>:
- Đại diện cho kết quả của một tác vụ bất đồng bộ (asynchronous).
- Có thể: kiểm tra đã xong chưa (
isDone()), lấy kết quả (get()), hủy task (cancel()).
- Thread Pool:
- Nhóm các thread được tái sử dụng để tránh chi phí tạo/destroy thread nhiều lần.
- Một số loại pool hay dùng:
Executors.newFixedThreadPool(n)→ số thread cố định.Executors.newCachedThreadPool()→ tạo thread khi cần, tái sử dụng khi rảnh.Executors.newSingleThreadExecutor()→ chỉ có 1 thread.Executors.newScheduledThreadPool(n)→ chạy task trễ hoặc định kỳ.
🔹 3. Ví dụ cơ bản
javaCopyEditimport java.util.concurrent.*;
public class ExecutorExample {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2);
// Runnable (không có kết quả trả về)
executor.submit(() -> {
System.out.println("Task 1 chạy trên " + Thread.currentThread().getName());
});
// Callable (có kết quả trả về)
Future<Integer> future = executor.submit(() -> {
System.out.println("Task 2 chạy trên " + Thread.currentThread().getName());
return 42;
});
// Lấy kết quả từ Callable
Integer result = future.get();
System.out.println("Kết quả: " + result);
// Đóng Executor
executor.shutdown();
}
}
Kết quả (ví dụ):
arduinoCopyEditTask 1 chạy trên pool-1-thread-1
Task 2 chạy trên pool-1-thread-2
Kết quả: 42
🔹 4. Các method quan trọng
submit(Runnable/Callable)→ gửi task, trả vềFuture.invokeAll(Collection<Callable>)→ chạy tất cả task, trả về listFuture.invokeAny(Collection<Callable>)→ lấy kết quả của task hoàn thành đầu tiên.shutdown()→ ngừng nhận task mới, chờ task hiện tại chạy xong.shutdownNow()→ cố gắng dừng tất cả task ngay lập tức.awaitTermination(timeout, unit)→ chờ cho tới khi tất cả task kết thúc hoặc hết thời gian.
🔹 5. Cách hoạt động bên trong
- Bạn tạo
ExecutorServicevới một thread pool. - Khi gọi
submit(), task sẽ được đưa vào hàng đợi (queue). - Các worker thread trong pool sẽ lấy task từ queue để thực thi.
- Nếu task là
Callable, kết quả sẽ được wrap trongFuture. - Khi gọi
shutdown(), Executor không nhận task mới nữa, đợi task cũ xong rồi thoát.
🔹 6. Lợi ích
- ✅ Tái sử dụng thread → tiết kiệm tài nguyên, tránh overhead.
- ✅ Quản lý dễ dàng → tập trung vào task thay vì thread.
- ✅ Hỗ trợ bất đồng bộ qua
Future. - ✅ Mở rộng tốt → xử lý nhiều task cùng lúc hiệu quả.
- ✅ Có thể lập lịch (schedule) cho task chạy sau hoặc lặp lại.
✅ Tóm lại:ExecutorService là một bộ quản lý thread pool trong Java. Bạn gửi task (Runnable/Callable), nó tự phân phối cho các thread trong pool thực hiện, và trả về kết quả qua Future. Nó giúp code gọn gàng, hiệu quả, và dễ mở rộng khi làm việc với đa luồng.
What’s the difference between wait() and sleep()?
Answer
🔹 1. sleep()
- Thuộc class
Thread(Thread.sleep(millis)). - Tạm dừng (pause) thread hiện tại trong một khoảng thời gian xác định.
- Sau khi hết thời gian, thread sẽ tự động tiếp tục chạy.
- Không giải phóng (release) lock/monitor nếu thread đang giữ.
- Thường dùng để tạo delay hoặc mô phỏng chờ đợi có thời gian.
Ví dụ:
javaCopyEditsynchronized void demoSleep() throws InterruptedException {
System.out.println("Start");
Thread.sleep(2000); // tạm dừng 2 giây
System.out.println("End");
}
🔹 2. wait()
- Thuộc class
Object(mọi object trong Java đều có). - Chỉ có thể gọi
wait()khi thread đang giữ monitor lock (tức là bên trongsynchronized). - Khi gọi
wait():- Thread sẽ tạm dừng.
- Đồng thời giải phóng lock để thread khác có thể vào
synchronized block.
- Thread sẽ được đánh thức lại bằng:
notify()→ đánh thức một thread đangwait.notifyAll()→ đánh thức tất cả thread đangwait.
Ví dụ:
javaCopyEditsynchronized void demoWait() throws InterruptedException {
System.out.println("Thread vào wait");
wait(); // thread nhả lock và chờ notify
System.out.println("Thread được đánh thức");
}
🔹 3. Bảng so sánh nhanh
| Tiêu chí | sleep() | wait() |
|---|---|---|
| Thuộc lớp nào | Thread | Object |
Cần synchronized? | ❌ Không | ✅ Phải gọi trong synchronized |
| Giải phóng lock? | ❌ Không | ✅ Có |
| Đánh thức bằng gì? | Tự động sau khi hết thời gian | notify() hoặc notifyAll() từ thread khác |
| Mục đích chính | Tạm dừng thread trong thời gian cố định | Cơ chế giao tiếp giữa các thread |
🔹 4. Tóm lại
wait() → thread tạm dừng và giải phóng lock, chờ thread khác gọi notify()/notifyAll(), dùng cho thread synchronization/communication.
sleep() → cho thread “ngủ” trong một khoảng thời gian, không giải phóng lock, dùng để tạo delay.
What is volatile? How does it work?
Answer
🔹 1. volatile là gì?
volatilelà một từ khóa (keyword) trong Java, dùng để khai báo biến.- Nó báo cho JVM và Compiler rằng:
- Biến này sẽ được chia sẻ giữa nhiều thread.
- Luôn luôn đọc/ghi trực tiếp từ bộ nhớ chính (main memory) thay vì từ cache CPU hoặc bản sao trong thread-local.
👉 Nếu không dùng volatile, một thread có thể giữ bản sao cũ của biến trong cache và không thấy thay đổi từ thread khác.
🔹 2. Cách hoạt động
- Bình thường, mỗi thread có thể cache biến → gây visibility problem (thread này update nhưng thread khác không thấy ngay).
- Khi khai báo
volatile:- Mỗi lần ghi (write) → giá trị sẽ được flush ngay xuống main memory.
- Mỗi lần đọc (read) → sẽ lấy trực tiếp từ main memory.
🔹 3. Ví dụ không dùng volatile (có bug)
javaCopyEditclass SharedData {
boolean running = true;
void run() {
while (running) {
// làm việc
}
}
}
SharedData data = new SharedData();
new Thread(data::run).start();
// Ở thread khác:
data.running = false; // Thread run() có thể KHÔNG thấy thay đổi này
👉 Có thể dẫn đến vòng lặp vô hạn do thread giữ giá trị running = true trong cache.
🔹 4. Ví dụ dùng volatile (an toàn hơn)
javaCopyEditclass SharedData {
volatile boolean running = true;
void run() {
while (running) {
// làm việc
}
}
}
👉 Khi một thread gán running = false, thread khác sẽ nhìn thấy ngay và thoát khỏi vòng lặp.
🔹 5. Điểm cần lưu ý
- Đảm bảo tính nhìn thấy (visibility)
- Các thay đổi của biến
volatileluôn được nhìn thấy bởi mọi thread.
- Các thay đổi của biến
- Không đảm bảo tính nguyên tử (atomicity)
- Ví dụ
count++không an toàn ngay cả khicountlàvolatile. - Muốn an toàn phải dùng
synchronizedhoặcAtomicInteger.
- Ví dụ
- Không lock
- Khác với
synchronized,volatilekhông khóa thread. - Nhanh và nhẹ hơn, nhưng yếu hơn.
- Khác với
- Quan hệ happens-before
- Một lần ghi vào biến
volatilesẽ xảy ra trước (happens-before) mọi lần đọc sau đó → giúp tránh reorder (tái sắp xếp lệnh) bởi JVM/CPU.
- Một lần ghi vào biến
🔹 6. Khi nào dùng volatile?
- Dùng khi:
- Biến chia sẻ giữa nhiều thread.
- Update chỉ là gán giá trị đơn giản (boolean flag, status, signal stop).
- Không dùng khi:
- Cần các thao tác phức tạp, như
count++,check-then-act. - Khi đó phải dùng
synchronizedhoặc các class trongjava.util.concurrent(vd:AtomicInteger).
- Cần các thao tác phức tạp, như
✅ Tóm lại
Thích hợp dùng cho các cờ trạng thái (flag, signal, boolean).
volatile đảm bảo visibility (các thread thấy cùng một giá trị mới nhất).
Không đảm bảo atomicity (các phép toán nhiều bước vẫn không an toàn).
What is the difference between Runnable and Thread?
Answer
🔹 1. Thread
Threadlà một class trong Java (java.lang.Thread).- Đại diện cho một luồng thực thi trong JVM.
- Có thể tạo thread bằng cách kế thừa (extends) class
Threadvà override phương thứcrun().
Ví dụ:
javaCopyEditclass MyThread extends Thread {
public void run() {
System.out.println("Thread đang chạy...");
}
}
public class Test {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // bắt đầu luồng mới
}
}
🔹 2. Runnable
Runnablelà một functional interface (java.lang.Runnable) có 1 phương thức duy nhấtrun().- Đại diện cho một nhiệm vụ (task) để thực thi, nhưng không phải là thread.
- Để chạy, bạn phải đưa
Runnablevào trong mộtThread.
Ví dụ:
javaCopyEditclass MyRunnable implements Runnable {
public void run() {
System.out.println("Runnable task đang chạy...");
}
}
public class Test {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
t1.start();
}
}
Hoặc với lambda (Java 8+):
javaCopyEditThread t1 = new Thread(() -> System.out.println("Runnable với lambda"));
t1.start();
🔹 3. So sánh chi tiết
| Tiêu chí | Thread | Runnable |
|---|---|---|
| Kiểu | Là một class (extends Thread) | Là một interface (implements Runnable) |
| Kế thừa | Kế thừa Thread nghĩa là không thể kế thừa class khác (Java không hỗ trợ đa kế thừa). | Với Runnable, bạn vẫn có thể kế thừa class khác. |
| Tách biệt nhiệm vụ | Vừa chứa logic task vừa chứa logic thread. | Chỉ chứa task, thread thực thi được tạo bên ngoài. |
| Chia sẻ tài nguyên | Khó chia sẻ nhiệm vụ giữa nhiều thread. | Một Runnable có thể được nhiều Thread chạy song song. |
| Thực tế dùng | Ít dùng, chỉ khi muốn custom thread class. | Được khuyến khích sử dụng cho task song song. |
🔹 4. Kết luận
Thread= người công nhân (thực thi).Runnable= công việc mà người công nhân thực hiện.
👉 Thực tế hiện nay (nhất là khi dùng ExecutorService), gần như luôn dùng Runnable hoặc Callable, chứ ít khi kế thừa Thread.
🔹 Design & Best Practices
What is SOLID principle?
Answer
🔹 1. S — Single Responsibility Principle (SRP)
- A class should have only one reason to change.
- In other words, each class should only do one job/responsibility.
✅ Benefit: Keeps code easier to maintain and test.
Example (Bad):
javaCopyEditclass Report {
void generateReport() {}
void saveToFile() {}
void printReport() {}
}
👉 This class handles generation, persistence, and printing → multiple responsibilities.
Better (SRP applied):
javaCopyEditclass ReportGenerator { void generate() {} }
class ReportSaver { void save() {} }
class ReportPrinter { void print() {} }
🔹 2. O — Open/Closed Principle (OCP)
- Software entities (classes, modules, functions) should be open for extension but closed for modification.
- You should be able to add new functionality without changing existing code.
✅ Benefit: Avoids breaking existing code when adding features.
Example (using polymorphism):
javaCopyEditinterface Shape { double area(); }
class Circle implements Shape { double area() { return ...; } }
class Rectangle implements Shape { double area() { return ...; } }
// Add new shape without modifying existing ones
class Triangle implements Shape { double area() { return ...; } }
🔹 3. L — Liskov Substitution Principle (LSP)
- Objects of a superclass should be replaceable with objects of its subclasses without breaking the program.
- Subtypes must behave like their parent type.
✅ Benefit: Prevents inheritance misuse.
Bad Example:
javaCopyEditclass Bird { void fly() {} }
class Ostrich extends Bird { void fly() { throw new UnsupportedOperationException(); } }
👉 Ostrich violates LSP because not all birds can fly.
Better:
javaCopyEditinterface Bird {}
interface FlyableBird extends Bird { void fly(); }
class Sparrow implements FlyableBird { public void fly() {} }
class Ostrich implements Bird {}
🔹 4. I — Interface Segregation Principle (ISP)
- Clients should not be forced to implement methods they don’t use.
- Better to have many small, specific interfaces than one large, general-purpose interface.
✅ Benefit: Prevents “fat interfaces”.
Bad Example:
javaCopyEditinterface Machine {
void print();
void scan();
void fax();
}
class OldPrinter implements Machine {
public void print() {}
public void scan() { throw new UnsupportedOperationException(); }
public void fax() { throw new UnsupportedOperationException(); }
}
Better:
javaCopyEditinterface Printer { void print(); }
interface Scanner { void scan(); }
interface Fax { void fax(); }
class OldPrinter implements Printer {
public void print() {}
}
🔹 5. D — Dependency Inversion Principle (DIP)
- High-level modules should not depend on low-level modules.
- Both should depend on abstractions (interfaces).
- Abstractions should not depend on details; details should depend on abstractions.
✅ Benefit: Improves flexibility and testability (e.g., using dependency injection).
Bad Example:
javaCopyEditclass MySQLDatabase {
void connect() {}
}
class UserService {
private MySQLDatabase db = new MySQLDatabase();
}
Better (DIP applied):
javaCopyEditinterface Database { void connect(); }
class MySQLDatabase implements Database { public void connect() {} }
class PostgreSQLDatabase implements Database { public void connect() {} }
class UserService {
private Database db;
UserService(Database db) { this.db = db; }
}
✅ Summary (SOLID)
D → Depend on abstractions, not concrete implementations.
S → One class = One responsibility.
O → Extend without modifying existing code.
L → Subclasses must behave like parent class.
I → Many small interfaces > one fat interface.
How do you implement Singleton correctly?
Answer
🔹 1. What is Singleton?
A design pattern where a class has only one instance during the entire application lifecycle, and it provides a global access point to that instance.
🔹 2. Common Implementations
(a) Eager Initialization
- Instance is created at class loading.
- Simple but may waste memory if unused.
javaCopyEditpublic class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {} // prevent instantiation
public static Singleton getInstance() {
return INSTANCE;
}
}
(b) Lazy Initialization (not thread-safe)
- Creates instance only when needed.
- But not safe in multithreaded environments.
javaCopyEditpublic class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
(c) Thread-Safe Singleton (synchronized method)
- Works but
synchronizedmakes it slower.
javaCopyEditpublic class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
(d) Double-Checked Locking (Best Pre-Java 5 fix needed)
- Improves performance by synchronizing only once when instance is created.
- Must declare
instanceasvolatile.
javaCopyEditpublic class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // First check (no locking)
synchronized (Singleton.class) {
if (instance == null) { // Second check (with locking)
instance = new Singleton();
}
}
}
return instance;
}
}
(e) Bill Pugh Singleton (Recommended)
- Uses a static inner helper class → thread-safe, lazy-loaded, no synchronization overhead.
javaCopyEditpublic class Singleton {
private Singleton() {}
// Inner static helper class
private static class SingletonHelper {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHelper.INSTANCE;
}
}
(f) Enum Singleton (Most Robust)
- Since Java enums provide implicit guarantee of single instance, serialization, and thread-safety.
javaCopyEditpublic enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("Doing something...");
}
}
Usage:
javaCopyEditSingleton.INSTANCE.doSomething();
🔹 3. Which one to use?
- Enum Singleton → Best for most cases (simple, safe against serialization & reflection).
- Bill Pugh Singleton → Best if you prefer class-based approach.
- Avoid naive lazy initialization without synchronization in multithreaded apps.
✅ Summary:
Bill Pugh or Enum Singleton are the modern, correct implementations.
Singleton ensures only one instance.
Thread safety + lazy initialization are key concerns.
Explain Dependency Injection.
Answer
🔹 1. Dependency Injection là gì?
- Dependency (phụ thuộc): là một đối tượng mà class khác cần để hoạt động.
- Injection (tiêm vào): là cách chúng ta cung cấp dependency từ bên ngoài, thay vì để class tự tạo ra.
👉 DI là một design pattern giúp giảm sự ràng buộc chặt chẽ (tight coupling), làm cho code dễ bảo trì, dễ mở rộng và dễ test.
👉 Đây cũng là một phần quan trọng của khái niệm Inversion of Control (IoC).
🔹 2. Ví dụ không dùng Dependency Injection (Tightly Coupled)
javaCopyEditclass MySQLDatabase {
void connect() { System.out.println("Kết nối MySQL"); }
}
class UserService {
private MySQLDatabase db = new MySQLDatabase(); // Tự tạo dependency
void process() { db.connect(); }
}
UserServicegắn chặt với MySQLDatabase.- Nếu muốn đổi sang PostgreSQL, phải sửa code của
UserService.
🔹 3. Ví dụ dùng Dependency Injection (Loosely Coupled)
javaCopyEditinterface Database {
void connect();
}
class MySQLDatabase implements Database {
public void connect() { System.out.println("Kết nối MySQL"); }
}
class PostgreSQLDatabase implements Database {
public void connect() { System.out.println("Kết nối PostgreSQL"); }
}
class UserService {
private Database db;
// Inject qua constructor
public UserService(Database db) {
this.db = db;
}
void process() { db.connect(); }
}
Sử dụng:
javaCopyEditpublic class Main {
public static void main(String[] args) {
Database db = new PostgreSQLDatabase(); // Tiêm phụ thuộc từ bên ngoài
UserService service = new UserService(db);
service.process(); // Kết nối PostgreSQL
}
}
👉 UserService không cần biết dùng DB nào, chỉ cần một Database.
🔹 4. Các kiểu Dependency Injection
- Constructor Injection → tiêm qua constructor (tốt nhất). javaCopyEdit
public UserService(Database db) { this.db = db; } - Setter Injection → tiêm qua setter method. javaCopyEdit
public void setDatabase(Database db) { this.db = db; } - Field Injection → tiêm trực tiếp vào field (hay dùng trong Spring với
@Autowired, nhưng khó test hơn). javaCopyEdit@Autowired private Database db;
🔹 5. Dependency Injection trong Spring
Spring có IoC Container quản lý dependency.
javaCopyEdit@Component
class UserService {
private final Database db;
@Autowired
public UserService(Database db) {
this.db = db;
}
}
- Spring sẽ tự tìm
Databaseimplementation (MySQL, PostgreSQL, …) và inject vàoUserService. - Lập trình viên không cần
newđối tượng nữa.
🔹 6. Lợi ích của Dependency Injection
✅ Giảm tight coupling (ràng buộc chặt chẽ).
✅ Dễ thay thế implementation (MySQL ↔ PostgreSQL).
✅ Dễ unit test (inject mock object).
✅ Dễ bảo trì, dễ mở rộng.
✅ Tóm lại:
Spring Framework là ví dụ phổ biến nhất của DI.
DI = “Đừng để tôi tự tạo dependency, hãy đưa nó cho tôi”.
DI giúp code linh hoạt, dễ test, dễ bảo trì.
🔹 JVM Knowledge
What are the memory areas in JVM?
Answer
Explain Garbage Collection phases.
Answer
🔹 1. Garbage Collection là gì?
- Là cơ chế quản lý bộ nhớ tự động trong JVM.
- GC sẽ giải phóng các đối tượng trong Heap mà không còn tham chiếu nào trỏ tới (unreachable).
- Mục tiêu: tránh memory leak và giảm lỗi
OutOfMemoryError.
🔹 2. Các giai đoạn chính của Garbage Collection
✅ Giai đoạn 1: Mark (đánh dấu)
- GC sẽ bắt đầu từ GC Roots (như biến local trong stack, thread đang chạy, static field, JNI references).
- Nó duyệt object graph để tìm các object còn sống.
- Những object nào reachable → được đánh dấu là “alive”.
- Những object không đánh dấu → được coi là rác (garbage).
👉 Nghĩa là GC hỏi: “Ai còn sống?”.
✅ Giai đoạn 2: Sweep (quét dọn)
- GC đi qua toàn bộ heap.
- Giải phóng vùng nhớ của các object không được đánh dấu.
- Các object alive vẫn giữ nguyên.
- Kết quả: có nhiều “lỗ trống” rải rác → gây fragmentation (phân mảnh bộ nhớ).
✅ Giai đoạn 3: Compact (nén/ gom lại)
- (Không phải lúc nào cũng có, tùy thuật toán GC).
- GC sẽ di chuyển các object sống lại gần nhau.
- Cập nhật lại các tham chiếu.
- Kết quả: Heap gọn gàng, có vùng nhớ trống liền mạch để cấp phát nhanh hơn.
🔹 3. Generational Garbage Collection (thế hệ)
Java Heap chia thành:
- Young Generation (Eden + Survivor)
- Nơi tạo object mới.
- Dọn dẹp thường xuyên bằng Minor GC (nhanh).
- Old Generation (Tenured)
- Nơi chứa object sống lâu.
- Dọn bằng Major GC / Full GC (tốn kém, stop-the-world lâu).
- Metaspace (trước kia là PermGen)
- Chứa metadata của class, method.
🔹 4. Ví dụ đơn giản
javaCopyEditpublic void testGC() {
String s1 = new String("hello"); // nằm trên heap
String s2 = new String("world");
s1 = null; // "hello" không còn tham chiếu → eligible for GC
System.gc(); // Gợi ý JVM chạy GC (không đảm bảo)
}
"hello"sẽ bị GC thu gom."world"còn tham chiếus2→ vẫn sống.
🔹 5. Tóm tắt
JVM tối ưu bằng Generational GC → Minor GC (nhanh), Major/Full GC (chậm hơn).
Mark → tìm object còn sống.
Sweep → xóa object chết.
Compact → gom bộ nhớ tránh phân mảnh.
What is the difference between stack and heap memory?
Answer
https://lukeiscoder.com/2025/04/15/heap-va-stack-memory/
🔹 Spring Framework (if applicable)
Difference between @Component, @Service, and @Repository.
Answer
🔹 1. Điểm chung
- Cả 3 đều là Spring stereotype annotations.
- Đều đánh dấu bean để Spring IoC Container quản lý.
- Khi Spring quét package (component scan), các class có annotation này sẽ được đưa vào ApplicationContext.
👉 Tóm lại: cả 3 đều là @Component, nhưng dùng cho mục đích khác nhau (semantic khác).
🔹 2. @Component
- Annotation tổng quát nhất.
- Dùng để đánh dấu một class là bean.
- Không phân biệt rõ class này thuộc tầng nào.
Ví dụ:
javaCopyEdit@Component
public class EmailValidator {
public boolean isValid(String email) {
return email.contains("@");
}
}
👉 Thích hợp cho các utility class hoặc bean chung không thuộc rõ Service/Repository.
🔹 3. @Service
- Là một specialization của @Component.
- Dùng cho tầng Service (business logic).
- Giúp code dễ đọc hơn, cho thấy class này chứa logic nghiệp vụ.
- Khi dùng AOP (Aspect-Oriented Programming), Spring có thể áp dụng behavior đặc biệt cho @Service.
Ví dụ:
javaCopyEdit@Service
public class UserService {
public void registerUser(String username) {
System.out.println("Register user: " + username);
}
}
🔹 4. @Repository
- Cũng là specialization của @Component.
- Dùng cho tầng DAO / Persistence (làm việc với database).
- Spring cung cấp exception translation:
- Bắt lỗi JDBC/Hibernate (SQLException, PersistenceException, …).
- Chuyển thành DataAccessException (runtime exception của Spring).
Ví dụ:
javaCopyEdit@Repository
public class UserRepository {
public void save(User user) {
// giả sử lưu vào DB
System.out.println("Saving user: " + user.getName());
}
}
🔹 5. Tóm tắt sự khác nhau
| Annotation | Tầng sử dụng | Đặc điểm chính |
|---|---|---|
| @Component | Chung, tiện ích | Đánh dấu bean tổng quát |
| @Service | Business logic | Cho thấy class chứa nghiệp vụ |
| @Repository | Persistence/DAO | Hỗ trợ exception translation với DB |
✅ Kết luận:
Về ý nghĩa thiết kế, dùng đúng annotation giúp code rõ ràng, dễ bảo trì, dễ cho developer khác hiểu ngay class này thuộc tầng nào.
Về kỹ thuật, cả 3 đều là @Component (Spring xử lý giống nhau).
What is the Spring Bean lifecycle?
Answer
🔹 Vòng đời (Lifecycle) của một Spring Bean
Khi Spring quản lý một bean trong IoC Container, nó sẽ trải qua nhiều giai đoạn:
1. Instantiation (Khởi tạo)
- Spring tạo instance của bean bằng constructor hoặc factory method.
- Ví dụ:
new MyService()hoặc thông qua@Bean.
2. Populate Properties (Tiêm dependencies)
- Spring thực hiện Dependency Injection (DI).
- Gán các dependency cho bean (thông qua
@Autowired,@Value, setter, constructor).
3. BeanNameAware & BeanFactoryAware (Tùy chọn)
- Nếu bean implement
BeanNameAware→ Spring sẽ gọisetBeanName(). - Nếu bean implement
BeanFactoryAware→ Spring gọisetBeanFactory(). - Cho phép bean biết mình được Spring quản lý như thế nào.
4. BeanPostProcessor (before init)
- Trước khi chạy init method, Spring cho phép can thiệp vào bean thông qua
BeanPostProcessor.postProcessBeforeInitialization().
5. Initialization (Khởi tạo sau khi inject)
- Nếu bean implement
InitializingBean→ gọiafterPropertiesSet(). - Nếu khai báo
@Bean(initMethod="...")hoặc dùng@PostConstruct→ gọi method này. - Đây là nơi bạn viết logic chuẩn bị dữ liệu, mở kết nối.
6. BeanPostProcessor (after init)
- Sau khi init, Spring tiếp tục gọi
BeanPostProcessor.postProcessAfterInitialization(). - Thường được dùng cho proxy wrapping (AOP, transaction).
7. Bean Ready (Sẵn sàng sử dụng)
- Lúc này bean có thể được sử dụng bởi ứng dụng.
8. Destruction (Hủy bean khi context đóng)
- Khi ApplicationContext shutdown:
- Nếu bean implement
DisposableBean→ gọidestroy(). - Nếu khai báo
@PreDestroyhoặc@Bean(destroyMethod="...")→ gọi method này.
- Nếu bean implement
- Đây là nơi để giải phóng tài nguyên (đóng file, connection pool, thread, socket).
🔹 Sơ đồ tóm tắt
cssCopyEdit[Khởi tạo] → [Inject dependencies]
→ [BeanPostProcessor.beforeInit]
→ [@PostConstruct / afterPropertiesSet / initMethod]
→ [BeanPostProcessor.afterInit]
→ [Bean ready]
→ [@PreDestroy / destroy()]
🔹 Ví dụ minh họa
javaCopyEdit@Component
public class MyBean implements InitializingBean, DisposableBean {
@PostConstruct
public void init() {
System.out.println("PostConstruct: init bean");
}
@Override
public void afterPropertiesSet() {
System.out.println("afterPropertiesSet: bean init");
}
@PreDestroy
public void cleanup() {
System.out.println("PreDestroy: cleanup");
}
@Override
public void destroy() {
System.out.println("destroy: bean destroyed");
}
}
👉 Khi chạy ứng dụng:
- Bean được khởi tạo
- Inject dependencies
- Gọi
@PostConstruct→afterPropertiesSet - Bean sẵn sàng
- Khi context tắt →
@PreDestroy→destroy
✅ Kết luận:
Spring Bean lifecycle gồm các bước: Khởi tạo → Inject → Init → Sẵn sàng → Destroy.
Bạn có thể hook vào các giai đoạn bằng @PostConstruct, @PreDestroy, InitializingBean, DisposableBean, hoặc BeanPostProcessor.
What is @Transactional used for?
Answer
🔹 @Transactional dùng để làm gì?
@Transactional được dùng trong Spring để quản lý giao dịch (transaction management) khi làm việc với cơ sở dữ liệu.
Nó giúp đảm bảo:
- Atomicity (Tính nguyên tử) → Tất cả các thao tác trong transaction phải thành công hết, nếu lỗi thì rollback toàn bộ.
- Consistency (Nhất quán) → Sau khi transaction kết thúc, dữ liệu phải ở trạng thái hợp lệ.
- Isolation (Cách ly) → Các transaction không làm ảnh hưởng sai lệch đến nhau.
- Durability (Bền vững) → Khi transaction commit, dữ liệu sẽ được lưu vĩnh viễn.
(Đây chính là ACID principle trong database).
🔹 Cách hoạt động
- Khi bạn đánh dấu method/class với
@Transactional:- Spring sẽ tạo một proxy bao quanh method đó.
- Khi method được gọi → Spring mở transaction.
- Nếu method chạy thành công → transaction được commit.
- Nếu method ném ra RuntimeException hoặc Error → transaction sẽ rollback.
🔹 Ví dụ
javaCopyEdit@Service
public class AccountService {
@Autowired
private AccountRepository accountRepository;
@Transactional
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
Account from = accountRepository.findById(fromId).get();
Account to = accountRepository.findById(toId).get();
from.debit(amount);
to.credit(amount);
accountRepository.save(from);
accountRepository.save(to);
// nếu có RuntimeException xảy ra -> rollback toàn bộ
}
}
👉 Nếu giữa chừng gặp lỗi (ví dụ số dư âm, lỗi DB) → tất cả thay đổi sẽ bị rollback.
👉 Nếu chạy thành công → dữ liệu được commit.
🔹 Các thuộc tính quan trọng
propagation→ Xác định cách transaction lan truyền giữa các method (ví dụ: REQUIRED, REQUIRES_NEW, NESTED).isolation→ Mức độ cách ly (READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE).timeout→ Thời gian tối đa cho một transaction.readOnly→ Đánh dấu transaction chỉ để đọc, giúp tối ưu hiệu năng.rollbackFor→ Xác định exception nào thì rollback (mặc định chỉ rollback RuntimeException).
🔹 Tóm tắt dễ nhớ
| Thuộc tính | Ý nghĩa |
|---|---|
@Transactional | Quản lý transaction trong DB |
| Commit | Khi method chạy thành công |
| Rollback | Khi có RuntimeException / Error |
propagation | Cách transaction lan truyền |
isolation | Mức độ cách ly giữa transaction |
✅ Kết luận:@Transactional đảm bảo tính an toàn dữ liệu khi thao tác với DB, giúp lập trình viên không cần viết thủ công BEGIN/COMMIT/ROLLBACK.
🔹 So sánh các propagation khi gọi lồng nhau
| Propagation (inner) | Gọi từ outer (REQUIRED) | Ảnh hưởng commit/rollback |
|---|---|---|
REQUIRED (mặc định) | Tham gia cùng transaction của outer | Inner lỗi → rollback toàn bộ outer + inner (vì là 1 transaction) |
REQUIRES_NEW | Treo (suspend) outer, tạo transaction mới | Inner lỗi → rollback chỉ inner; outer không bị ảnh hưởng. Ngược lại outer lỗi không rollback được inner đã commit |
NESTED | Dùng savepoint bên trong cùng 1 transaction vật lý | Inner lỗi → rollback đến savepoint (chỉ phần inner), outer tiếp tục được. Outer lỗi → rollback toàn bộ (vượt qua mọi savepoint) |
SUPPORTS | Dùng chung nếu có; nếu không thì chạy không transaction | Phụ thuộc outer; nếu không có outer thì không có rollback/commit TX |
MANDATORY | Yêu cầu đang có TX | Không có TX → ném exception |
NOT_SUPPORTED | Tạm ngưng TX hiện tại, chạy không TX | Không rollback gì vì không TX |
NEVER | Cấm có TX | Nếu có TX → ném exception |
REQUIRES_NEW vs NESTED
REQUIRES_NEW: hai transaction vật lý độc lập. Inner commit/rollback không phụ thuộc outer.NESTED: một transaction vật lý, inner là savepoint. Rollback inner chỉ quay về savepoint; nhưng nếu outer rollback cuối cùng, mọi thứ đều mất.
Senior Level (5+ years)
🔹 Advanced Java
How does the Java memory model work?
Answer
Explain CompletableFuture and non-blocking programming.
Answer
Explain ClassLoaders and custom ClassLoader use cases.
Answer
How do you handle performance tuning for a high-load Java application?
Answer
🔹 Architecture & Design
How do you design a microservice architecture in Java?
Answer
What is Circuit Breaker pattern?
Answer
How do you ensure thread safety in concurrent applications?
Answer
Explain CAP theorem and how it applies in Java-based distributed systems.
Answer
🔹 Framework Mastery
Deep dive: How does Spring Boot autoconfiguration work?
Answer
Explain how AOP (Aspect-Oriented Programming) works in Spring.
Answer
How would you create a custom annotation in Java?
Answer
🔹 System Design / Integration
How do you handle retries, idempotency, and timeouts in external API calls?
Answer
What’s the difference between REST and gRPC? When to use each?
Answer
How do you design a scalable Java-based backend system?
Answer
Topic: Collections
Beginner level
What is the difference between List, Set, and Map?
Answer
1. List
- Ordered collection of elements
- Duplicates allowed
- Access by index (like array)
- Examples:
ArrayList,LinkedList
Use when:
- You care about order
- You might have duplicate values
- You need to access items by position
2. Set
- Unordered collection (some types may preserve order like
LinkedHashSet) - No duplicates allowed
- Each element must be unique
- Examples:
HashSet,TreeSet,LinkedHashSet
Use when:
Order is not important (unless using specific Set types)
You need to avoid duplicates
3. Map
- Key-value pairs
- Keys are unique, values can be duplicated
- No index-based access
- Examples:
HashMap,TreeMap,LinkedHashMap
Use when:
- You want to map a key to a value
- You want to look up by key
What is the difference between ArrayList and LinkedList?
Answer
What are the key differences between HashSet and TreeSet?
Answer
What is the difference between HashMap and Hashtable?
Answer
How does HashMap work internally and what is load factor in HashMap?
Answer
Intermediate-level
What is the difference between Comparable and Comparator?
Answer
What is the difference between Iterator and ListIterator?
Answer
What is the difference between Fail-fast and fail-safe?
Answer
What are ConcurrentHashMap and how is it different from Collections.synchronizedMap()?
Answer
How does sorting work in collections? (e.g., Collections.sort())
What is the use of Queue and Deque interfaces?
Answer
Advance level
How does CopyOnWriteArrayList work and when should it be used?
Answer
What are the pros and cons of HashMap vs ConcurrentHashMap?
Answer
What is the role of equals() and hashCode() in collections?
Answer
What are the internal implementations of LinkedList, HashSet, and TreeMap?
Answer
What happens when two keys have the same hashcode in a HashMap?
Answer
Be First to Comment