Hướng dẫn xử lý ngoại lệ trong Java - Java Exception Handling
1. Exception là gì?
Trước hết chúng ta hãy xem một ví dụ minh họa sau:
Trong ví dụ này có một đoạn code lỗi nguyên nhân do phép chia cho 0. Việc chia cho 0 gây ra ngoại lệ: ArithmeticException
Trong ví dụ này có một đoạn code lỗi nguyên nhân do phép chia cho 0. Việc chia cho 0 gây ra ngoại lệ: ArithmeticException
HelloException.java
package org.o7planning.tutorial.exception;
public class HelloException {
public static void main(String[] args) {
System.out.println("Three");
// Phép chia này không có vấn đề.
int value = 10 / 2;
System.out.println("Two");
// Phép chia này không có vấn đề.
value = 10 / 1;
System.out.println("One");
// Phép chia này có vấn đề, chia cho 0.
// Lỗi đã xẩy ra tại đây.
value = 10 / 0;
// Và dòng code dưới đây sẽ không được thực hiện.
System.out.println("Let's go!");
}
}
Kết quả chạy ví dụ:
Bạn có thể thấy thông báo lỗi trên màn hình Console, thông báo lỗi rất rõ ràng, xẩy ra ở dòng thứ mấy trên code.
Bạn có thể thấy thông báo lỗi trên màn hình Console, thông báo lỗi rất rõ ràng, xẩy ra ở dòng thứ mấy trên code.
Three
Two
One
Exception in thread "main" java.lang.ArithmeticException: / by zero
at org.o7planning.tutorial.exception.HelloException.main(HelloException.java:31)
Hãy xem luồng đi của chương trình qua hình minh họa dưới đây.
- Chương trình đã chạy hoàn toàn bình thường từ các bước (1),(2) cho tới (5)
- Bước thứ (6) xẩy ra vấn đề khi chia cho 0.
- Chương trình đã nhẩy ra khỏi hàm main, và dòng code thứ (7) đã không được thực hiện.
Chúng ta sẽ sửa code của ví dụ trên.
HelloCatchException.java
package org.o7planning.tutorial.exception;
public class HelloCatchException {
public static void main(String[] args) {
System.out.println("Three");
// Phép chia này hoàn toàn không có vấn đề.
int value = 10 / 2;
System.out.println("Two");
// Phép chia này cũng vậy.
value = 10 / 1;
System.out.println("One");
try {
// Phép chia này có vấn đề, chia cho 0.
// Một lỗi đã xẩy ra tại đây.
value = 10 / 0;
// Dòng code này sẽ không được thực thi.
System.out.println("Value =" + value);
} catch (ArithmeticException e) {
// Các dòng code trong catch được thực thi.
System.out.println("Error: " + e.getMessage());
// Các dòng code trong catch được thực thi.
System.out.println("Ignore...");
}
// Dòng code này sẽ được thực thi.
System.out.println("Let's go!");
}
}
Và kết quả chạy ví dụ:
Three
Two
One
Error: / by zero
Ignore...
Let's go!
Chúng ta sẽ giải thích bằng hình minh họa dưới đây về luồng đi của chương trình.
- Các bước (1)-(5) hoàn toàn bình thường.
- Ngoại lệ xẩy ra tại bước (6), vấn đề chia cho 0.
- Lập tức nó nhẩy vào thực thi lệnh trong khối catch, bước (7) bị bỏ qua.
- Bước (8),(9) được thực hiện.
- Bước (10) được thực hiện.
2. Hệ thống phân cấp ngoại lệ
Đây là mô hình sơ đồ phân cấp của Exception trong java.
- Class ở mức cao nhất là Throwable
- Hai class con trực tiếp là Error và Exception.
Chú ý: Các class tùy biến của bạn nên viết thừa kế từ 2 nhánh Error hoặc Exception, không viết thừa kế trực tiếp từ Throwable.
Error
Khi liên kết động thất bại, hoặc trong máy ảo xẩy ra một vấn đề nghiêm trọng, nó sẽ ném ra một Error. Các chương trình Java điển hình không nên bắt lỗi (Error). Ngoài ra, nó không chắc rằng các chương trình Java điển hình sẽ bao giờ ném lỗi
Ví dụ về liên kết động: Chẳng hạn, khi bạn gọi đến thư viện mà thư viện đó thiếu class, hoặc thiếu method,... trong trường hợp vậy Error sẽ bị ném ra.
Exceptions
Hầu hết các chương trình ném và bắt các đối tượng là con của class Exception. Trường hợp Exception cho thấy một vấn đề xảy ra nhưng vấn đề không phải là một vấn đề mang tính hệ thống nghiêm trọng. Hầu hết các chương trình bạn viết sẽ ném và bắt Exception.
Class Exception có nhiều class con cháu được định nghĩa trong gói Java. Những hậu duệ cho nhiều loại hình trường hợp ngoại lệ có thể xảy ra. Ví dụ, NegativeArraySizeException được ném ra khi bạn cố gắng tạo một mảng (array) mà lại có số phần tử âm.
Một class ngoại lệ con có ý nghĩa đặc biệt trong ngôn ngữ Java: RuntimeException.
Class Exception có nhiều class con cháu được định nghĩa trong gói Java. Những hậu duệ cho nhiều loại hình trường hợp ngoại lệ có thể xảy ra. Ví dụ, NegativeArraySizeException được ném ra khi bạn cố gắng tạo một mảng (array) mà lại có số phần tử âm.
Một class ngoại lệ con có ý nghĩa đặc biệt trong ngôn ngữ Java: RuntimeException.
Runtime Exceptions
Class RuntimeException đại diện cho trường hợp ngoại lệ xảy ra trong thời gian chạy chương trình. Một ví dụ về một ngoại lệ thời gian chạy là NullPointerException, xảy ra khi một bạn truy cập vào method hoặc field một đối tượng thông qua một tham chiếu null. Với các ngoại lệ kiểu này người ta thường kiểm tra để đảm bảo rằng đối tượng này khác null, hơn là tìm bắt ngoại lệ.
Bởi vì trường hợp ngoại lệ thời gian chạy rất phổ biến và cố gắng bắt hoặc chỉ định tất cả chúng là cách làm không hiệu quả. Trình biên dịch của Java không kiểm tra các ngoại lệ này trong quá trình biên dịch code.
Java định nghĩa một vài lớp RuntimeException. Bạn có thể bắt (Catch) những trường hợp ngoại lệ này như cách bắt các ngoại lệ thông thường khác. Các method mà trong nó có thể ném ra RuntimeException cũng không đòi hỏi phải khai báo trên định nghĩa của nó. Ngoài ra, bạn có thể tạo lớp con RuntimeException của riêng bạn.
3. Bắt ngoại lệ thông qua try-catch
Chúng ta viết một exception thừa kế từ class Exception.
AgeException.java
package org.o7planning.tutorial.exception.basic;
public class AgeException extends Exception {
public AgeException(String message) {
super(message);
}
}
TooYoungException.java
package org.o7planning.tutorial.exception.basic;
public class TooYoungException extends AgeException {
public TooYoungException(String message) {
super(message);
}
}
TooOldException.java
package org.o7planning.tutorial.exception.basic;
public class TooOldException extends AgeException {
public TooOldException(String message) {
super(message);
}
}
Và class AgeUtils có method tĩnh dùng cho việc kiểm tra tuổi.
AgeUtils.java
package org.o7planning.tutorial.exception.basic;
public class AgeUtils {
// Phương thức này làm nhiệm vụ kiểm tra tuổi.
// Nếu tuổi nhỏ hơn 18 nó sẽ ném ra ngoại lệ TooYoungException
// Nếu tuổi lớn hơn 40 nó sẽ ném ra ngoại lệ TooOldException
public static void checkAge(int age) throws TooYoungException, TooOldException {
if (age < 18) {
// Nếu tuổi nhỏ hơn 18, một ngoại lệ sẽ được ném ra
// Phương thức này sẽ kết thúc tại đây.
throw new TooYoungException("Age " + age + " too young");
} else if (age > 40) {
// Nếu tuổi lớn hơn 40, ngoại lệ sẽ được ném ra.
// Method này kết thúc tại đây.
throw new TooOldException("Age " + age + " too old");
}
// Nếu tuổi nằm trong khoảng 18-40.
// Đoạn code này sẽ được chạy.
System.out.println("Age " + age + " OK!");
}
}
Checked Exception & Unchecked Exception:
- AgeException là con của Exception, TooOldException và TooYoungException là 2 class con trực tiếp của AgeException, nên chúng là các "Checked Exception"
- Trong method AgeUtils.checkAge(int) có ném ra ngoài các ngoại lệ này vì vậy trên khai báo của method bạn cần phải liệt kê chúng thông qua từ khóa "throws". Hoặc bạn có thể khai báo ném ra ở mức tổng quát hơn
- throws Exception.
- Tại các nơi sử dụng AgeUtils.checkAge(int) cũng phải có sử lý để bắt các ngoại lệ đó, hoặc tiếp tục ném ra vòng ngoài.
"Checked exception" sẽ được "Java Compiler" kiểm tra.
Bạn có hai sự lựa chọn sử lý:
- Ném tiếp ra vòng ngoài
- Thực hiện việc bắt và sử lý ngoại lệ thông qua try-catch.
TryCatchDemo1.java
package org.o7planning.tutorial.exception.basic;
public class TryCatchDemo1 {
public static void main(String[] args) {
// Bắt đầu tuyển dụng.
System.out.println("Start Recruiting ...");
// Kiểm tra tuổi.
System.out.println("Check your Age");
int age = 50;
try {
AgeUtils.checkAge(age);
System.out.println("You pass!");
} catch (TooYoungException e) {
// Làm gì đó tại đây ..
System.out.println("You are too young, not pass!");
System.out.println(e.getMessage());
} catch (TooOldException e) {
// Làm gì đó tại đây
System.out.println("You are too old, not pass!");
System.out.println(e.getMessage());
}
}
}
Ví dụ dưới đây, chúng ta sẽ gộp bắt các ngoại lệ thông qua ngoại lệ ở cấp cao hơn. Ở cấp cao hơn nó sẽ tóm được ngoại lệ đó và tất cả các ngoại lệ con.
TryCatchDemo2.java
package org.o7planning.tutorial.exception.basic;
public class TryCatchDemo2 {
public static void main(String[] args) {
// Bắt đầu tuyển dụng
System.out.println("Start Recruiting ...");
// Kiểm tra tuổi của bạn.
System.out.println("Check your Age");
int age = 15;
try {
// Chỗ này có thể phát ra ngoại lệ TooOldException,
// hoặc TooYoungException
AgeUtils.checkAge(age);
System.out.println("You pass!");
} catch (AgeException e) {
// Nếu có ngoại lệ xẩy ra, kiểu AgeException.
// Khối catch này sẽ được chạy.
System.out.println("Your age invalid, you not pass");
System.out.println(e.getMessage());
}
}
}
Bạn cũng có thể gộp xử lý các ngoại lệ khác nhau vào cùng một khối catch để sử lý nếu chúng có cách xử lý giống nhau trong logic chương trình của bạn.
TryCatchDemo3.java
package org.o7planning.tutorial.exception.basic;
public class TryCatchDemo3 {
public static void main(String[] args) {
System.out.println("Start Recruiting ...");
System.out.println("Check your Age");
int age = 15;
try {
// Chỗ này có thể gây ra ngoại lệ TooOldException,
// hoặc TooYoungException
AgeUtils.checkAge(age);
System.out.println("You pass!");
} catch (TooYoungException | TooOldException e) {
// Bắt nhiều loại ngoại lệ trong 1 khối Catch.
System.out.println("Your age invalid, you not pass");
System.out.println(e.getMessage());
}
}
}
4. Khối try-catch-finally
Trên kia chúng ta đã làm quen với việc bắt lỗi thông qua khối try-catch. Việc xử lý ngoại lệ đầy đủ là try-catch-finally.
try {
// Làm gì đó tại đây.
} catch (Exception1 e) {
// Làm gì đó tại đây.
} catch (Exception2 e) {
// Làm gì đó tại đây.
} finally {
// Khối finally luôn luôn được thực thi.
// Làm gì đó tại đây.
}
TryCatchFinallyDemo.java
package org.o7planning.tutorial.exception.basic;
public class TryCatchFinallyDemo {
public static void main(String[] args) {
String text = "001234A2";
int value = toInteger(text);
System.out.println("Value= " + value);
}
public static int toInteger(String text) {
try {
System.out.println("Begin parse text: " + text);
// Tại đây có thể gây ra ngoại lệ NumberFormatException.
int value = Integer.parseInt(text);
return value;
} catch (NumberFormatException e) {
// Trong trường hợp 'text' không phải là một số.
// Khối catch này sẽ được thực thi.
System.out.println("Number format exception " + e.getMessage());
// Khi NumberFormatException xẩy ra, trả về 0.
return 0;
} finally {
System.out.println("End parse text: " + text);
}
}
}
Đây là sơ luồng đi của chương trình. Khối finally luôn được thực thi.
5. Gói một Exception trong một Exception khác
Chúng ta cần một vài class tham gia vào ví dụ này:
- Person: Mô phỏng một người tham gia tuyển dụng vào công ty với các thông tin
- Tên, tuổi, giới tính.
- GenderException: Ngoại lệ giới tính.
- ValidateException: Ngoại lệ đánh giá thí sinh.
- ValidateUtils: Class có method tĩnh đánh giá thí sinh đủ tiêu chuẩn không.
- Tiêu chuẩn là những người độ tuổi 18-40
- Và là Nam.
Person.java
package org.o7planning.tutorial.exception.wrap;
public class Person {
public static final String MALE = "male";
public static final String FEMALE = "female";
private String name;
private String gender;
private int age;
public Person(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
GenderException.java
package org.o7planning.tutorial.exception.wrap;
// Ngoại lệ Giới tính.
public class GenderException extends Exception {
public GenderException(String message) {
super(message);
}
}
Class ValidateException bao lấy một Exception khác.
ValidateException.java
package org.o7planning.tutorial.exception.wrap;
public class ValidateException extends Exception {
// Gói (wrap) Exception trong một Exception khác
public ValidateException(Exception e) {
super(e);
}
}
ValidateUtils.java
package org.o7planning.tutorial.exception.wrap;
import org.o7planning.tutorial.exception.basic.AgeUtils;
public class ValidateUtils {
// Phương thức kiểm tra 1 ứng viên.
public static void checkPerson(Person person) throws ValidateException {
try {
// Kiểm tra tuổi.
// Hợp lệ là trong khoảng 18-40
// Method này có thể ném ra TooOldException,TooYoungException.
AgeUtils.checkAge(person.getAge());
} catch (Exception e) {
// Nếu không hợp lệ
// Gói ngoại lệ này bởi ValidateException, và ném ra (throw).
throw new ValidateException(e);
}
// Nếu người này là Nữ, nghĩa là không hợp lệ.
if (person.getGender().equals(Person.FEMALE)) {
GenderException e = new GenderException("Do not accept women");
throw new ValidateException(e);
}
}
}
WrapperExceptionDemo.java
package org.o7planning.tutorial.exception.wrap;
public class WrapperExceptionDemo {
public static void main(String[] args) {
// Một ứng viên.
Person person = new Person("Marry", Person.FEMALE, 20);
try {
// Ngoại lệ có thể xẩy ra tại đây.
ValidateUtils.checkPerson(person);
} catch (ValidateException wrap) {
// Lấy ra nguyên nhân thực sự.
// Mà có thể là TooYoungException, TooOldException, GenderException.
Exception cause = (Exception) wrap.getCause();
if (cause != null) {
System.out.println("Not pass, cause: " + cause.getMessage());
} else {
System.out.println(wrap.getMessage());
}
}
}
}
6. RuntimeException và các class con
Class RuntimeException và các class con, cháu của nó đều là các "Unchecked exception". Nó không được bộ dịch java kiểm tra trong thời gian biên dịch. Trong một vài tình huống bạn có thể viết các exception của mình thừa kế từ nhánh này. Có một số ngoại lệ trong nhánh này sẵn có trong java mà bạn cần phải để mắt tới nó.
Dưới đây là một vài class thuộc nhánh RuntimeException (Tất nhiên không phải là tất cả).
Dưới đây là một vài class thuộc nhánh RuntimeException (Tất nhiên không phải là tất cả).
Chúng ta thử một vài ví dụ xử lý các ngoại lệ kiểu này:
NullPointerException
Đây là một trong các ngoại lệ thông dụng nhất, và hay gây ra lỗi cho chương trình. Ngoại lệ được ném ra khi bạn gọi phương thức hoặc truy cập vào các trường của một đối tượng chưa được khởi tạo (đối tượng null).
NullPointerExceptionDemo.java
package org.o7planning.tutorial.exception.runtime;
public class NullPointerExceptionDemo {
// Ví dụ đây là một method mà có thể trả về chuỗi null.
public static String getString() {
if (1 == 2) {
return "1==2 !!";
}
return null;
}
public static void main(String[] args) {
// Đây là một biến có tham chiếu khác null.
String text1 = "Hello exception";
// Gọi phương thức để lấy ra độ dài chuỗi.
int length = text1.length();
System.out.println("Length text1 = " + length);
// Đây là một biến có tham chiếu null.
String text2 = getString();
// Gọi phương thức để lấy ra độ dài chuỗi.
// NullPointerException sẽ xẩy ra tại đây.
// Nó là ngoại lệ xuất hiện tại thời gian chạy (runtime).
// (Kiểu RuntimeException).
// Trình biên dịch (compiler) của Java không bắt buộc
// bạn phải bắt (catch) nó tại thời điểm biên dịch (compile-time).
length = text2.length(); // ==> exception!
System.out.println("Finish!");
}
}
Kết quả chạy ví dụ:
Length text1 = 15
Exception in thread "main" java.lang.NullPointerException
at org.o7planning.tutorial.exception.runtime.NullPointerExceptionDemo.main(NullPointerExceptionDemo.java:51)
Trong thực tế giống việc xử lý các ngoại lệ khác, bạn có thể sử dụng try-catch để bắt ngoại lệ này mà xử lý. Tuy nhiên, đó là cách máy móc, thông thường chúng ta nên kiểm tra để đảm bảo rằng đối tượng là khác null trước khi sử dụng nó.
Bạn có thể sửa code trên giống dưới đây, để tránh NullPointerException:
Bạn có thể sửa code trên giống dưới đây, để tránh NullPointerException:
// Phương thức getString() trả về một giá trị null.
// Đây là một đối tượng có tham chiếu null.
String text2 = getString();
// Kiểm tra để đảm bảo rằng text2 là khác null.
// Thay vì sử dụng try-catch.
if (text2 != null) {
length = text2.length();
}
ArrayIndexOfBoundException
Đây là ngoại lệ nó được ném ra khi bạn cố truy cập vào phần tử có chỉ số không hợp lệ trên mảng. Chẳng hạn mảng có 10 phần tử, mà bạn lại truy cập vào phần tử có chỉ số 20.
ArrayIndexOfBoundsExceptionDemo.java
package org.o7planning.tutorial.exception.runtime;
public class ArrayIndexOfBoundsExceptionDemo {
public static void main(String[] args) {
String[] strs = new String[] { "One", "Two", "Three" };
// Truy cập vào phần tử tại chỉ số 0.
String str1 = strs[0];
System.out.println("String at 0 = " + str1);
// Truy cập vào phần tử tại chỉ số 5
// ArrayIndexOfBoundsException xẩy ra tại đây.
String str2 = strs[5];
System.out.println("String at 5 = " + str2);
}
}
Để tránh ArrayIndexOfBoundsException bạn nên kiểm tra mảng thay vì sử dụng try-catch.
if (strs.length > 5) {
String str2 = strs[5];
System.out.println("String at 5 = " + str2);
} else {
System.out.println("No elements with index 5");
}
Java cơ bản
- Tùy biến trình biên dịch java xử lý Annotation của bạn (Annotation Processing Tool)
- Lập trình Java theo nhóm sử dụng Eclipse và SVN
- Hướng dẫn và ví dụ Java WeakReference
- Hướng dẫn và ví dụ Java PhantomReference
- Hướng dẫn nén và giải nén trong Java
- Cấu hình Eclipse để sử dụng JDK thay vì JRE
- Phương thức String.format() và printf() trong Java
- Cú pháp và các tính năng mới trong Java 5
- Cú pháp và các tính năng mới trong Java 8
- Hướng dẫn sử dụng biểu thức chính quy trong Java
- Hướng dẫn lập trình đa luồng trong Java - Java Multithreading
- Thư viện điều khiển các loại cơ sở dữ liệu khác nhau trong Java
- Hướng dẫn sử dụng Java JDBC kết nối cơ sở dữ liệu
- Lấy các giá trị của các cột tự động tăng khi Insert một bản ghi sử dụng JDBC
- Hướng dẫn và ví dụ Java Stream
- Functional Interface trong Java
- Giới thiệu về Raspberry Pi
- Hướng dẫn và ví dụ Java Predicate
- Abstract class và Interface trong Java
- Access modifier trong Java
- Hướng dẫn và ví dụ Java Enum
- Hướng dẫn và ví dụ Java Annotation
- So sánh và sắp xếp trong Java
- Hướng dẫn và ví dụ Java String, StringBuffer và StringBuilder
- Hướng dẫn xử lý ngoại lệ trong Java - Java Exception Handling
- Hướng dẫn và ví dụ Java Generics
- Thao tác với tập tin và thư mục trong Java
- Hướng dẫn và ví dụ Java BiPredicate
- Hướng dẫn và ví dụ Java Consumer
- Hướng dẫn và ví dụ Java BiConsumer
- Bắt đầu với Java cần những gì?
- Lịch sử của Java và sự khác biệt giữa Oracle JDK và OpenJDK
- Cài đặt Java trên Windows
- Cài đặt Java trên Ubuntu
- Cài đặt OpenJDK trên Ubuntu
- Cài đặt Eclipse
- Cài đặt Eclipse trên Ubuntu
- Học nhanh Java cho người mới bắt đầu
- Lịch sử của bit và byte trong khoa học máy tính
- Các kiểu dữ liệu trong Java
- Các toán tử Bitwise
- Câu lệnh rẽ nhánh (if else) trong Java
- Câu lệnh rẽ nhánh switch trong Java
- Vòng lặp trong Java
- Mảng (Array) trong Java
- JDK Javadoc định dạng CHM
- Thừa kế và đa hình trong Java
- Hướng dẫn và ví dụ Java Function
- Hướng dẫn và ví dụ Java BiFunction
- Ví dụ về Java encoding và decoding sử dụng Apache Base64
- Hướng dẫn và ví dụ Java Reflection
- Hướng dẫn gọi phương thức từ xa với Java RMI
- Hướng dẫn lập trình Java Socket
- Các nền tảng nào bạn nên chọn để lập trình ứng dụng Java Desktop?
- Hướng dẫn và ví dụ Java Commons IO
- Hướng dẫn và ví dụ Java Commons Email
- Hướng dẫn và ví dụ Java Commons Logging
- Tìm hiểu về Java System.identityHashCode, Object.hashCode và Object.equals
- Hướng dẫn và ví dụ Java SoftReference
- Hướng dẫn và ví dụ Java Supplier
- Lập trình Java hướng khía cạnh với AspectJ (AOP)
Show More
- Hướng dẫn lập trình Java Servlet/JSP
- Các hướng dẫn Java Collections Framework
- Java API cho HTML & XML
- Các hướng dẫn Java IO
- Các hướng dẫn Java Date Time
- Các hướng dẫn Spring Boot
- Các hướng dẫn Maven
- Các hướng dẫn Gradle
- Các hướng dẫn Java Web Services
- Các hướng dẫn lập trình Java SWT
- Các hướng dẫn lập trình JavaFX
- Các hướng dẫn Java Oracle ADF
- Các hướng dẫn Struts2 Framework
- Các hướng dẫn Spring Cloud