Đa Khối Lệnh catch (Multiple Catch Block)
Trong Java, bạn có thể sử dụng nhiều khối catch sau một khối try để xử lý các loại ngoại lệ khác nhau. Mỗi khối catch sẽ chuyên trách xử lý một loại ngoại lệ cụ thể. Điều này cho phép bạn xử lý từng loại ngoại lệ một cách cụ thể, tăng tính linh hoạt và kiểm soát trong việc xử lý ngoại lệ.
Ⅰ. Cách thức hoạt động của đa khối catch
Khi một ngoại lệ xảy ra trong khối try, chương trình sẽ tìm kiếm khối catch phù hợp nhất để xử lý.
Việc tìm kiếm bắt đầu từ khối catch đầu tiên. Nếu ngoại lệ trùng khớp với loại ngoại lệ được khai báo trong khối catch đó, thì khối catch đó sẽ được thực thi và các khối catch còn lại sẽ bị bỏ qua.
Nếu không có khối catch nào phù hợp, ngoại lệ sẽ được truyền lên các cấp gọi hàm cho đến khi tìm thấy một khối catch phù hợp hoặc chương trình bị dừng đột ngột.

Dưới đây là một ví dụ minh họa về cách sử dụng nhiều khối catch:
Ví dụ: Example.java
public class Example {
public static void main(String[] args) {
try {
// Khối lệnh có thể ném ra nhiều loại ngoại lệ
int[] numbers = {1, 2, 3};
System.out.println(numbers[5]); // Sẽ gây ra ArrayIndexOutOfBoundsException
int result = 10 / 0; // Sẽ gây ra ArithmeticException
System.out.println(result);
} catch (ArrayIndexOutOfBoundsException e) {
// Bắt ngoại lệ ArrayIndexOutOfBoundsException
System.out.println("Ngoại lệ: Chỉ số mảng vượt quá giới hạn.");
} catch (ArithmeticException e) {
// Bắt ngoại lệ ArithmeticException
System.out.println("Ngoại lệ: Lỗi số học, chia cho 0.");
} catch (Exception e) {
// Bắt bất kỳ ngoại lệ nào khác không được bắt ở trên
System.out.println("Ngoại lệ chung: " + e.getMessage());
}
System.out.println("Chương trình tiếp tục chạy sau khối try-catch.");
}
}
Kết quả của chương trình là:
Chương trình tiếp tục chạy sau khối try-catch.
Trong ví dụ trên, nếu numbers[5] gây ra ngoại lệ, chỉ khối catch cho ArrayIndexOutOfBoundsException sẽ được thực thi, và khối catch cho ArithmeticException và Exception sẽ bị bỏ qua.
Tương tự, nếu int result = 10 / 0; gây ra ngoại lệ, chỉ khối catch cho ArithmeticException sẽ được thực thi.
Sử dụng nhiều khối catch giúp bạn xử lý các loại ngoại lệ khác nhau một cách hiệu quả và giữ cho chương trình của bạn hoạt động đúng cách ngay cả khi có lỗi xảy ra.
Ⅱ. Nguyên tắc sắp xếp các khối catch trong Java
Tại sao thứ tự lại quan trọng?
Khi một ngoại lệ được ném ra trong khối try, JVM sẽ tìm kiếm khối catch đầu tiên có thể xử lý ngoại lệ đó. Nếu tìm thấy, khối catch đó sẽ được thực thi và các khối catch còn lại sẽ bị bỏ qua. Do đó, nếu bạn đặt một khối catch cho ngoại lệ chung trước khối catch cho ngoại lệ cụ thể, thì ngoại lệ cụ thể sẽ không bao giờ được bắt được.
Nguyên tắc chi tiết:
↳ Từ cụ thể đến chung: Các khối catch cho các ngoại lệ cụ thể (là con của một lớp ngoại lệ khác) phải được đặt trước các khối catch cho ngoại lệ chung (là lớp cha).
↳ Tránh trùng lặp: Không nên có hai khối catch có thể xử lý cùng một loại ngoại lệ. Điều này sẽ gây ra lỗi biên dịch.
↳ Khối catch cuối cùng: Khối catch cho Exception thường được đặt cuối cùng để bắt tất cả các ngoại lệ không được các khối catch trước đó bắt được. Tuy nhiên, nên hạn chế sử dụng khối catch này vì nó có thể che giấu các lỗi cụ thể và làm khó khăn trong việc gỡ lỗi.
Ⅲ. Hệ thống cấp bậc ngoại lệ
Dưới đây là một danh sách các ngoại lệ phổ biến trong Java được tổ chức theo thứ tự từ cha đến con:
├──Exception
│ ├── RuntimeException (Ngoại lệ không bắt buộc phải xử lý)
│ │ ├── ArithmeticException
│ │ ├── ArrayIndexOutOfBoundsException
│ │ ├── ClassCastException
│ │ ├── IllegalArgumentException
│ │ ├── NumberFormatException
│ │ ├── IllegalStateException
│ │ ├── NullPointerException
│ │ ├── IndexOutOfBoundsException
│ │ ├── ArrayIndexOutOfBoundsException
│ │ ├── StringIndexOutOfBoundsException
│ │ ├── UnsupportedOperationException
│ │ └── ConcurrentModificationException
│ ├── IOException (Java I/O)
│ │ ├── FileNotFoundException
│ │ ├── EOFException
│ │ ├── CharConversionException
│ │ ├── InterruptedIOException
│ │ ├── ObjectStreamException
│ │ ├── InvalidClassException
│ │ ├── InvalidObjectException
│ │ └── UnsupportedEncodingException
│ ├── SQLException (Cơ sở dữ liệu)
│ │ ├── BatchUpdateException
│ │ ├── SQLTimeoutException
│ │ ├── SQLDataException
│ │ └── SQLIntegrityConstraintViolationException
│ ├──ReflectiveOperationException (Reflection API)
│ │ ├── ClassNotFoundException
│ │ ├── NoSuchMethodException
│ │ ├── IllegalAccessException
│ │ ├── InvocationTargetException
│ │ └── InstantiationException
│ ├── GeneralSecurityException (Bảo mật và mã hóa)
│ │ ├── KeyStoreException
│ │ ├── NoSuchAlgorithmException
│ │ ├── CertificateException
│ │ ├── UnrecoverableEntryException
│ │ ├── UnrecoverableKeyException
│ │ ├── InvalidKeyException
│ │ ├── InvalidAlgorithmParameterException
│ │ ├── SignatureException
│ │ └── KeyManagementException
│ ├── ParseException (Phân tích và chuyển đổi dữ liệu)
│ ├── DatatypeConfigurationException (Kiểu dữ liệu XML)
│ │ ├── TransformerConfigurationException
│ │ └── TransformerFactoryConfigurationError
│ ├── TransformerException (Chuyển đổi XML)
│ ├── MarshalException (Chuyển đổi đối tượng XML)
│ ├── XMLParseException (Phân tích cú pháp XML)
│ ├── XMLSignatureException (Chữ ký số XML)
│ ├── XMLStreamException (Luồng xử lý XML)
│ ├── XPathException (Truy vấn XPath)
│ ├── InterruptedException (Lập trình đa luồng)
│ ├── ExecutionException (Thực thi tác vụ bất đồng bộ)
│ ├── TimeoutException (Thời gian chờ)
│ ├── BrokenBarrierException (Đồng bộ hóa đa luồng)
│ ├── GeneralSecurityException (Bảo mật chung)
│ │ └── GSSException
│ ├── AWTException (Giao diện người dùng)
│ ├── PrinterException (In ấn)
│ ├── UnsupportedLookAndFeelException (Giao diện không được hỗ trợ)
│ ├── NamingException (Tra cứu tên trong dịch vụ thư mục)
│ ├── ActivationException (Cấu hình và hệ thống)
│ ├── JMException (Quản lý Java Management)
│ ├── ScriptException (Thực thi mã kịch bản)
│ ├── SOAPException (Xử lý SOAP)
│ ├── ServerNotActiveException (Truy cập máy chủ không hoạt động)
├── Error (Lỗi nghiêm trọng)
│ ├── VirtualMachineError (Lỗi máy ảo Java nghiêm trọng)
│ │ ├── StackOverflowError (Lỗi tràn ngăn xếp)
│ │ └── OutOfMemoryError (Lỗi hết bộ nhớ)
│ ├── AssertionError (Lỗi kiểm tra điều kiện assert thất bại)
│ ├── LinkageError (Lỗi liên kết lớp)
│ │ ├── ClassNotFoundException (Lỗi không tìm thấy lớp)
│ │ ├── NoClassDefFoundError (Lỗi không thể tải lớp)
│ │ └── UnsupportedClassVersionError (Lỗi phiên bản lớp không được hỗ trợ)
│ ├── FactoryConfigurationError (Lỗi cấu hình trình tạo đối tượng)
│ ├── ServiceConfigurationError (Lỗi cấu hình dịch vụ)
Lưu ý:
↳ Throwable là lớp cơ sở của tất cả các ngoại lệ và lỗi trong Java.
↳ Exception và Error là hai lớp con chính của Throwable.
↳ Exception có thể chia thành hai loại: Checked Exception và Unchecked Exception (RuntimeException là lớp cha của Unchecked Exception).
↳ Error đại diện cho các lỗi nghiêm trọng mà các ứng dụng thường không thể phục hồi.
↳ Việc hiểu rõ hệ thống cấp bậc này giúp bạn dễ dàng xác định và xử lý các ngoại lệ trong Java một cách chính xác.
Để thấy rõ sự cụ thể trong nguyên tắc sắp xếp các khối catch từ cụ thể đến chung chung, chúng ta có thể xem xét một ví dụ minh họa trong Java. Ví dụ này sẽ bắt một ngoại lệ cụ thể trước, sau đó mới đến các ngoại lệ chung hơn.
Ví dụ minh họa đúng nguyên tắc (Cụ thể trước, chung sau)
Ví dụ: Example.java
public class Example {
public static void main(String[] args) {
try {
int[] numbers = {1, 2, 3};
System.out.println(numbers[5]); // Gây ra ArrayIndexOutOfBoundsException
} catch (ArrayIndexOutOfBoundsException e) {
// Bắt ngoại lệ ArrayIndexOutOfBoundsException (cụ thể)
System.out.println("Ngoại lệ: Chỉ số mảng vượt quá giới hạn.");
} catch (IndexOutOfBoundsException e) {
// Bắt ngoại lệ IndexOutOfBoundsException (chung hơn)
System.out.println("Ngoại lệ: Vượt quá chỉ số.");
} catch (Exception e) {
// Bắt bất kỳ ngoại lệ nào khác không được bắt ở trên
System.out.println("Ngoại lệ chung: " + e.getMessage());
}
System.out.println("Chương trình tiếp tục chạy sau khối try-catch.");
}
}
Kết quả của chương trình là:
Chương trình tiếp tục chạy sau khối try-catch.
Ví dụ minh họa sai nguyên tắc (Chung trước, Cụ thể sau)
Nếu sắp xếp khối catch chung trước khối catch cụ thể, trình biên dịch sẽ báo lỗi:
Ví dụ: Example.java
public class Example {
public static void main(String[] args) {
try {
int[] numbers = {1, 2, 3};
System.out.println(numbers[5]); // Gây ra ArrayIndexOutOfBoundsException
} catch (Exception e) {
// Bắt tất cả các ngoại lệ, làm cho các khối 'catch' cụ thể bị vô hiệu
System.out.println("Ngoại lệ chung: " + e.getMessage());
} catch (IndexOutOfBoundsException e) {
// Lỗi biên dịch: Khối catch này sẽ không bao giờ được thực thi
System.out.println("Ngoại lệ: Vượt quá chỉ số.");
} catch (ArrayIndexOutOfBoundsException e) {
// Lỗi biên dịch: Khối catch này sẽ không bao giờ được thực thi
System.out.println("Ngoại lệ: Chỉ số mảng vượt quá giới hạn.");
}
}
}
Kết quả của chương trình là:
Giải thích:
↳ Đúng nguyên tắc: Khi ngoại lệ ArrayIndexOutOfBoundsException xảy ra, khối catch đầu tiên bắt nó và xử lý. Nếu ngoại lệ khác xảy ra, chẳng hạn như IndexOutOfBoundsException, khối catch thứ hai sẽ bắt nó.
↳ Sai nguyên tắc: Khối catch đầu tiên bắt ngoại lệ Exception, là ngoại lệ chung, sẽ bắt tất cả các ngoại lệ, làm cho các khối catch sau không bao giờ được thực thi.
Kết luận : Trong khối try-catch, luôn sắp xếp các khối catch từ cụ thể đến chung chung để đảm bảo rằng các ngoại lệ cụ thể được xử lý trước khi bị các ngoại lệ chung hơn bắt. Điều này đảm bảo rằng các ngoại lệ được xử lý đúng cách và chương trình không bị lỗi biên dịch.