try / catch / finally — cây Throwable và cơ chế throw
Cây Throwable (Error, Exception, RuntimeException), try/catch/finally, throw, multi-catch Java 7+. Anti-pattern nuốt exception. Phần cơ chế sâu (stack trace, cause chain, exception table) ở bài 2.
Chương trình Java gặp sự cố — file không tồn tại, chia cho 0, array index âm, null dereference — lúc đó phải làm gì? Ngôn ngữ cũ C trả error code: -1 nghĩa lỗi, 0 ok. Caller phải check return sau mọi lời gọi. Bỏ quên 1 check → bug silent. Code 80% là if-check, 20% là logic thật.
Java chọn cơ chế khác — exception: khi lỗi, method ném (throw) object mô tả lỗi, JVM tự unwind call stack cho đến khi tìm được catch xử lý. Caller không phải check return — exception bắt buộc được xử lý ở đâu đó (hoặc crash program).
Bài này đi qua cú pháp và rule cơ bản:
- Cây Throwable hierarchy — chọn exception nào khi throw.
try/catch/finallysemantic.throwchủ động và chọn exception class đúng.- Multi-catch Java 7+.
- Anti-pattern: nuốt exception.
Cơ chế sâu (stack trace build, exception table bytecode, cause chain, suppressed) đi vào bài kế tiếp — để hiểu vì sao throw rẻ và cách JVM handle exception runtime.
1. Analogy — báo cháy qua tầng
Toà nhà 10 tầng, tầng 7 phát cháy. Nhân viên tầng 7 có 2 lựa chọn:
- Tự xử: có bình cứu hoả → dùng → tiếp tục làm việc. Tương đương
catch+ continue. - Báo lên: không xử được → kích chuông báo động → tầng trên biết, đến giải quyết. Tương đương throw propagate.
Dù chọn gì, 1 việc luôn làm: đóng sổ theo dõi (ghi end-of-day). Đó là finally — luôn chạy dù xử lý được hay không.
Khác biệt quan trọng với error code: exception bắt buộc được xử lý ở đâu đó — không thể "quên check". Caller bỏ qua → exception propagate lên main → JVM in stack trace và crash.
| Đời thường | Java |
|---|---|
| Sự cố phát sinh | throw new Exception(...) |
| Tự xử | catch (Exception e) { ... } |
| Báo lên tầng trên | Không catch, propagate |
| Đóng sổ (luôn làm) | finally { ... } |
2. Cây Throwable — hierarchy exception
Đây là bức tranh bạn phải thuộc — hiểu cây này giúp quyết định catch gì, khi nào:
Throwable (goc cua tat ca, co stack trace)
├── Error (JVM loi nang - KHONG catch)
│ ├── OutOfMemoryError (heap het)
│ ├── StackOverflowError (recursion vo tan)
│ ├── NoClassDefFoundError (classpath sai)
│ └── ...
│
└── Exception (loi ung dung)
├── RuntimeException (unchecked - compiler KHONG ep)
│ ├── NullPointerException (NPE)
│ ├── IllegalArgumentException
│ ├── IllegalStateException
│ ├── IndexOutOfBoundsException
│ ├── ArithmeticException (chia 0)
│ ├── ClassCastException
│ ├── NumberFormatException
│ └── ...
│
└── (checked - compiler EP khai throws hoac catch)
├── IOException
│ ├── FileNotFoundException
│ └── EOFException
├── SQLException
├── ClassNotFoundException
├── InterruptedException
└── ...
Error — lỗi JVM nặng
- JVM gặp tình huống không chạy tiếp được: hết heap, stack overflow, class file corrupt.
- Không catch. Nếu catch được cũng không nên xử lý — JVM đã ở trạng thái không ổn định.
OutOfMemoryErrorvề lý thuyết bắt được, nhưng catch không giúp gì — thường heap đã cạn.
Exception — lỗi ứng dụng
Chia 2 nhóm dựa trên compiler behavior:
RuntimeExceptionvà con cháu → unchecked → compiler không ép khaithrowshay catch. Tự do.- Các
Exceptionkhác (IOException, SQLException, ClassNotFoundException, InterruptedException) → checked → compiler bắt phải khaithrowshoặc catch.
Chi tiết checked vs unchecked ở bài 3. Hiện tại nắm: hai nhóm có signature compiler khác nhau.
Exception phổ biến nhất
| Exception | Khi nào gặp |
|---|---|
NullPointerException | Dereference null: user.getName() khi user == null |
IllegalArgumentException | Argument không hợp lệ: sqrt(-1) |
IllegalStateException | State sai cho op: gọi method khi connection đã close |
IndexOutOfBoundsException | Array/List index ngoài giới hạn |
ArithmeticException | Chia cho 0 (integer) |
ClassCastException | Cast sai kiểu |
NumberFormatException | Parse số sai: Integer.parseInt("abc") |
IOException | Disk/network lỗi |
InterruptedException | Thread bị interrupt khi sleep/wait |
- NPE: tham chiếu null không mong đợi.
Objects.requireNonNull(x)throw NPE kèm message. - IllegalArgumentException: argument sai (giá trị, format). Validate input bằng cái này.
- IllegalStateException: state object sai cho operation (method called out of order).
- IndexOutOfBoundsException: index ngoài range.
- UnsupportedOperationException: operation không hỗ trợ (add vào immutable list).
Chọn đúng exception class giúp reader + stack trace hiểu intent.
3. Cú pháp try/catch/finally
try {
// code co the nem exception
} catch (SpecificException e) {
// xu ly loai loi cu the
} finally {
// cleanup - luon chay
}
Ví dụ cụ thể:
public static int parseOrDefault(String s, int defaultValue) {
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
System.out.println("Cannot parse: " + s + " - fallback");
return defaultValue;
}
}
parseOrDefault("42", 0); // 42
parseOrDefault("abc", 0); // 0
Rule cú pháp:
tryphải có ít nhất 1catch, hoặcfinally, hoặc cả hai.tryblock không thể đứng một mình.catchcụ thể trước, tổng quát sau.
4. Nhiều catch — thứ tự quan trọng
try {
openFile("a.txt");
parseContent();
} catch (FileNotFoundException e) {
System.out.println("File not found");
} catch (IOException e) {
System.out.println("I/O error: " + e.getMessage());
} catch (Exception e) {
System.out.println("Unknown error");
}
Compiler check: catch cụ thể phải trước catch tổng quát, vì runtime match từ trên xuống, dừng ở match đầu tiên.
try { ... }
catch (Exception e) { ... } // bat moi Exception
catch (IOException e) { ... } // COMPILE ERROR - unreachable
Compiler error: "exception IOException has already been caught".
Multi-catch (Java 7+)
Xử lý nhiều exception giống nhau trong 1 block:
try {
doSomething();
} catch (IOException | SQLException e) {
log.error("Data access failed", e);
throw new DataAccessException(e);
}
Rule:
- Các exception type không có quan hệ kế thừa (không thể
IOException | FileNotFoundExceptionvì FNE là con IOException). - Biến
elà common supertype — ở đây kiểu thực làException. Chỉ gọi được method chung.
5. finally — luôn chạy
Connection conn = null;
try {
conn = DriverManager.getConnection(url);
doWork(conn);
} catch (SQLException e) {
log.error("DB error", e);
throw new RuntimeException(e);
} finally {
if (conn != null) {
try { conn.close(); }
catch (SQLException e) { log.warn("Failed to close", e); }
}
}
finally chạy bất kể:
- Try chạy xong bình thường → finally chạy.
- Try ném exception, catch match → catch chạy → finally chạy.
- Try ném exception, không có catch match → finally chạy → exception propagate.
- Catch ném exception mới → finally chạy → exception mới propagate.
Chỉ 2 trường hợp finally KHÔNG chạy:
System.exit(n)— JVM tắt luôn, không cleanup.- JVM crash (kill -9, hardware fail).
Return trong finally — anti-pattern
public static int demo() {
try {
return 1;
} finally {
return 2; // override return cua try
}
}
// demo() tra 2
return trong finally override return của try/catch. Hầu như luôn là bug — mất giá trị tính trong try, mất exception được throw.
IDE (IntelliJ, Eclipse) warning "finally block does not complete normally". Sửa: không return trong finally.
Try-with-resources thay finally cleanup
Nếu cleanup là close resource, try-with-resources (bài 4) gọn hơn finally thủ công. Ví dụ trên viết lại:
try (Connection conn = DriverManager.getConnection(url)) {
doWork(conn);
} catch (SQLException e) {
log.error("DB error", e);
throw new RuntimeException(e);
}
// conn tu dong close, khong can finally
6. throw — chủ động ném
public static int divide(int a, int b) {
if (b == 0) {
throw new ArithmeticException("Division by zero");
}
return a / b;
}
throw ném exception tại điểm đó. Execution dừng, JVM unwind stack tìm catch match. (Cơ chế unwind chi tiết ở bài 2.)
Rule khi throw:
- Dùng
throw new <ExceptionClass>(message, cause). - Chọn exception class phù hợp ngữ nghĩa (xem bảng mục 2).
- Luôn message có ý — ghi cả giá trị gây lỗi để debug:
throw new IllegalArgumentException("age must be non-negative, got " + age);
7. Anti-patterns — AVOID
7.1 Nuốt exception — số 1
try {
doWork();
} catch (Exception e) {
// khong log, khong throw, khong lam gi
}
Anti-pattern số 1 của Java. Lỗi xảy ra, program chạy tiếp như chưa có → bug ẩn, data corrupt, user complain. Debug không có thông tin.
Fix:
- Log:
log.error("Failed to do work", e);(pass cả exception để có stack trace). - Rethrow:
throw new RuntimeException(e);nếu không biết xử lý. - Domain wrap:
throw new OrderProcessingException("Step X failed", e);.
7.2 Catch Exception hoặc Throwable tổng quát
try {
doX();
} catch (Exception e) { // Che cac bug code
log.error("Error", e);
}
Catch Exception phủ lớn che NullPointerException, ClassCastException, etc. — thường là bug code cần fix, không phải handle.
Fix: catch chính xác exception bạn dự liệu (IOException, SQLException). Để RuntimeException propagate lên global handler.
7.3 printStackTrace() không log
try { ... } catch (Exception e) {
e.printStackTrace();
}
In ra System.err. Production server System.err thường không đi vào log aggregator. Lỗi vào void.
Fix: logger framework (SLF4J, Logback):
log.error("Operation X failed", e);
7.4 Catch InterruptedException không restore flag
try { Thread.sleep(1000); }
catch (InterruptedException e) {
// khong lam gi
}
InterruptedException khi throw clear interrupt flag. Code caller check Thread.isInterrupted() không thấy tín hiệu → thread không dừng. Bài 10.1 đã chi tiết.
Fix: restore:
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
8. Pitfall tổng hợp
❌ Nhầm 1: Nuốt exception. ✅ Log + rethrow hoặc domain wrap.
❌ Nhầm 2: Catch Exception tổng quát che bug.
✅ Catch cụ thể; RuntimeException để propagate.
❌ Nhầm 3: return trong finally.
✅ Không return trong finally. Dùng try-with-resources cleanup.
❌ Nhầm 4: Thứ tự catch sai.
catch (Exception e) { ... }
catch (IOException e) { ... } // UNREACHABLE
✅ Cụ thể trước, tổng quát sau.
❌ Nhầm 5: printStackTrace() production.
✅ log.error(message, e);
9. 📚 Deep Dive Oracle
Spec / reference chính thức:
- JLS §14.20 — The try statement — cú pháp try/catch/finally.
- JLS §11 — Exceptions — lý thuyết, kinds.
- Throwable javadoc — API getMessage, getCause, getStackTrace.
- Effective Java Item 69 "Use exceptions only for exceptional conditions"; Item 77 "Don't ignore exceptions".
Ghi chú: Effective Java Item 69 — exception không dùng cho control flow thông thường (if/else). Dùng exception cho tình huống thực sự "bất thường" — đảm bảo hiệu năng và code readability.
10. Tóm tắt
- Cây Throwable:
Throwable → Error / Exception;Exception → RuntimeException (unchecked) / checked. Errorkhông catch — JVM lỗi nặng.Exception= lỗi ứng dụng.RuntimeExceptionunchecked. Checked cho low-level IO/DB.- Chọn exception class đúng semantic: NPE, IllegalArgumentException, IllegalStateException, IndexOutOfBoundsException.
try/catch/finally: catch cụ thể trước, tổng quát sau.finallyluôn chạy (trừ System.exit, JVM crash).- Không return trong finally — override try return.
- Multi-catch (Java 7+):
catch (A | B e)gộp xử lý. - Anti-pattern: nuốt exception, catch Exception tổng quát, printStackTrace production, InterruptedException không restore flag.
11. Tự kiểm tra
Q1Đoạn sau in gì và return bao nhiêu?public static int demo() {
try {
return 1;
} finally {
System.out.println("finally");
return 2;
}
}
System.out.println(demo());
▸
public static int demo() {
try {
return 1;
} finally {
System.out.println("finally");
return 2;
}
}
System.out.println(demo());In finally rồi 2.
Flow:
return 1trong try — value1được chuẩn bị trả.- Trước khi thực sự return, JVM chạy
finally. System.out.println("finally")in.return 2trong finally override return của try — method thật sự trả2.
return trong finally là anti-pattern — nuốt return try và có thể nuốt exception chưa propagate. IDE warning. Rule: chỉ side-effect cleanup trong finally.
Q2Đoạn sau compile không?try { ... }
catch (Exception e) { log.error(e); }
catch (IOException e) { log.error(e); }
▸
try { ... }
catch (Exception e) { log.error(e); }
catch (IOException e) { log.error(e); }Không compile. catch (Exception e) bắt mọi Exception, bao gồm IOException. catch (IOException e) sau sẽ không bao giờ match → compiler báo "exception IOException has already been caught".
Fix: đảo thứ tự — catch cụ thể trước, tổng quát sau:
try { ... }
catch (IOException e) { log.error(e); }
catch (Exception e) { log.error(e); }Compiler match theo thứ tự trên xuống, gặp match đầu tiên dừng.
Q3Vì sao nuốt exception là anti-pattern?▸
Exception được ném nhưng không ai biết. Hệ quả:
- Bug ẩn trong production: logic downstream chạy với data rỗng → bug ngẫu nhiên khó reproduce.
- Debug cực khó: không có log, không có stack trace, developer lội code tìm không ra.
- Mất observability: monitoring/metrics không biết có lỗi.
Fix:
- Log:
log.error("failed", e);— pass exception để có stack trace. - Rethrow:
throw new RuntimeException(e);. - Wrap domain:
throw new DataFetchException(e);.
Effective Java Item 77: "Don't ignore exceptions". Nếu thực sự muốn ignore (hiếm), comment rõ lý do.
Q4Cây Throwable có 3 tầng: Throwable → Error/Exception, Exception → RuntimeException/checked. Mỗi tầng catch khi nào?▸
Throwable: root. Catch bắt mọi thứ, cả Error. Không nên — catch Error (OOM, StackOverflow) không giúp gì vì JVM đã bất ổn. Chỉ dùng cho global uncaught handler.
Error: JVM errors (OutOfMemoryError, StackOverflowError, NoClassDefFoundError). Không catch. Fail fast — restart process.
Exception: application errors. 2 nhóm:
- RuntimeException (unchecked): thường là bug code (NPE, ClassCast). Không catch chỗ gọi — để propagate lên global handler, fix code gốc. Catch khi có fallback hợp lý (vd NumberFormatException parse user input → trả default).
- Checked Exception (IOException, SQLException): compiler ép khai throws hoặc catch. Catch nơi biết cách xử lý; nếu không, throw lên hoặc wrap thành RuntimeException.
Rule: catch càng cụ thể càng tốt.
Bài tiếp theo: Stack trace và cause chain — cơ chế bên dưới exception
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên