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?