Lỗi, Ngoại lệ (Exception) xảy ra là chuyện thường xuyên trong thế giới lập trình. Nó có thể là không hợp lệ dùng đầu vào hoặc một hệ thống bên ngoài mà không được đáp ứng, hoặc đó là một đơn giản lập trình lỗi.
Trong tất cả những tình huống lỗi hay ngoại lệ xảy ra chúng ta cần phải xử lý chúng. Nếu không, chương trình có thể bị treo và không thể tiếp tục quá trình được yêu cầu.
Java cung cấp một cơ chế mạnh mẽ, cho phép chúng ta để xử lý các sự kiện đặc biệt mà nó xảy ra ở một trong những phương thức cao hơn trong call stack.
Cách Chỉ định và Xử lý các Ngoại lệ trong Java (Exception trong Java)
Trong bài viết này, chúng ta sẽ đề cập đến vấn đề Ngoại lệ trong Java:
-
Thuật ngữ chung của Xử lý ngoại lệ Java (Exception trong Java)
-
Các ngoại lệ được kiểm tra và không được kiểm tra trong Java
-
Cách xử lý một ngoại lệ trong Java
-
Cách chỉ định ngoại lệ
-
Làm thế nào để biết nên xử lý hoặc chỉ định một ngoại lệ
Trước khi chúng ta đi vào các chi tiết từng bước của việc xử lý ngoại lệ trong Java, chúng ta cần phải xác định một vài điều khoản.
Thuật ngữ chung trong vấn đề ngoại lệ Java (Exception trong Java)
1. Call stack là gì? Ngăn xếp cuộc gọi là sao?
Call stack hay còn gọi là Ngăn xếp cuộc gọi là danh sách các phương thức đã được gọi để có được một phương thức cụ thể.
Trong ngữ cảnh của bài đăng này, đây là danh sách các phương thức được gọi để nhận được một lỗi.
Hãy thử xem một ví dụ: method1 gọi method2 mà method2 gọi medthod3. Ngăn xếp cuộc gọi hiện chứa ba mục sau đây:
2. Exception Class and Hierarchy: Lớp ngoại lệ và phân cấp
Lớp ngoại lệ (Exception Class) xác định loại lỗi xảy ra.
Ví dụ, NumberFormatException bị ném ra khi một Chuỗi có định dạng sai và không thể chuyển đổi thành một số.
Như mọi lớp Java, lớp ngoại lệ là một phần của hệ thống phân cấp kế thừa (inheritance hierarchy). Nó phải extend java.lang.Exception hoặc một trong các lớp con (subclass) của nó.
Hệ thống phân cấp cũng được sử dụng để nhóm các loại lỗi tương tự. Một ví dụ cho điều đó là IllegalArgumentException. Nó chỉ ra rằng một đối số phương thức được cung cấp là không hợp lệ và nó là superclass của NumberFormatException.
Bạn cũng có thể triển khai các lớp ngoại lệ của riêng mình bằng cách mở rộng lớp Exception hoặc bất kỳ lớp con nào của nó. Đoạn mã sau đây cho thấy một ví dụ đơn giản về một ngoại lệ tùy chỉnh (Custom Exception).
public class MyBusinessException extends Exception {
private static final long serialVersionUID = 7718828512143293558L;
public MyBusinessException() {
super();
}
public MyBusinessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public MyBusinessException(String message, Throwable cause) {
super(message, cause);
}
public MyBusinessException(String message) {
super(message);
}
public MyBusinessException(Throwable cause) {
super(cause);
}
}
3. Exception Object: Đối tượng ngoại lệ
Một đối tượng ngoại lệ là một thể hiện của một lớp ngoại lệ. Nó được tạo và trao cho Java runtime khi xảy ra một sự kiện đặc biệt làm gián đoạn dòng chảy thông thường của ứng dụng.
Cái này được gọi là "to throw an exception" bởi vì trong Java bạn sử dụng từ khóa "throw" ném ra để trao ngoại lệ cho runtime.
Khi một phương thức ném một đối tượng ngoại lệ, bộ thực thi sẽ tìm kiếm ngăn xếp cuộc gọi để tìm một đoạn mã xử lý nó.
Bạn sẽ được tìm hiểu thêm chi tiết về xử lý ngoại lệ trong Java ở phần Cách xử lý ngoại lệ của bài viết này.
Các ngoại lệ được kiểm tra và không được kiểm tra trong Java
Java hỗ trợ các ngoại lệ được kiểm tra (Checked Exception) và ngoại lệ không được kiểm tra (Unchecked Exception). Bạn có thể sử dụng chúng theo những cách tương tự, và có khá nhiều cuộc thảo luận về việc khi nào nên sử dụng loại ngoại lệ nào.
Nhưng điều đó vượt quá phạm vi của bài viết này. Nếu bạn muốn tìm hiểu rõ thì vui lòng xem Hướng dẫn của Oracle
Bạn nên sử dụng ngoại lệ được kiểm tra (Checked Exception) cho tất cả các sự kiện đặc biệt mà các bạn có thể dự đoán và đó cũng làm một ứng dụng tốt hơn.
Một ngoại lệ được kiểm tra extend Exception class. Một phương thức mà nó ném ra một ngoại lệ được kiểm tra hoặc gọi phương thức mà cần để kiểm tra một ngoại lệ được chỉ định cho dù là chỉ chỉ định hay xử lý ngoại lệ.
Các ngoại lệ không được kiểm tra extend RuntimeException. Bạn nên sử dụng chúng cho các lỗi nội bộ mà bạn không thể dự đoán.
Các phương thức có thể nhưng không cần xử lý hoặc chỉ định một ngoại lệ không được kiểm tra. Các ví dụ điển hình đưa ra các ngoại lệ không được kiểm tra là:
-
Việc khởi tạo bị thiếu của một biến dẫn đến một NullPulumException
-
Việc sử dụng API không đúng cách gây ra IllegalArgumentException
Cách Xử lý Ngoại lệ trong Java (Exception Handling trong Java)
Java cung cấp hai tùy chọn khác nhau để xử lý một ngoại lệ.
Bạn có thể sử dụng phương thức try catch finally để xử lý tất cả các loại ngoại lệ.
Hoặc bạn có thể sử dụng phương thức try-with-resource cho phép quá trình dọn dẹp tài nguyên dễ dàng hơn.
Try Catch Finally trong Java
Đây là cách tiếp cận cổ điển để xử lý một ngoại lệ trong Java (Exception trong Java). Để thực hiện chúng gồm 3 bước:
-
Một khối try bọc lấy phần mã có thể sẽ ném ra ngoại lệ
-
Một khối catch để bắt và xử lý ngoại lệ được ném ra
-
Một khối finally cùng được thực thi sau khi khối try được thực thi thành công hoặc một ngoại lệ ném ra được xử lý.
Khối try là cần có, và bạn có thể sử dụng nó mà có hoặc không có catch hay finaly.
Ví dụ về Khối Try trong Java Exception
Hãy nói về những khối try đầu tiên.
Khối try bao quanh một phần của mã mà bạn dự đoán nó có thể ném ngoại lệ trong quá trình thực thi.
Nếu mã của bạn ném nhiều hơn một ngoại lệ, bạn có thể lựa chọn nếu bạn muốn:
-
Sử dụng một khối try riêng cho mỗi câu lệnh có thể đưa ra một ngoại lệ
-
Sử dụng một khối try cho nhiều câu lệnh có thể đưa ra nhiều ngoại lệ.
Ví dụ sau đây cho thấy một khối thử bao gồm ba lệnh gọi phương thức.
public void performBusinessOperation() {
try {
doSomething("A message");
doSomethingElse();
doEvenMore();
}
// see following examples for catch and finally blocks
}
public void doSomething(String input) throws MyBusinessException {
// do something useful ...
throw new MyBusinessException("A message that describes the error.");
}
public void doSomethingElse() {
// do something else ...
}
public void doEvenMore() throws NumberFormatException{
// do even more ...
}
Như bạn có thể thấy trong các định nghĩa phương thức, chỉ có phương thức thứ nhất và thứ ba chỉ định một ngoại lệ.
Cái đầu tiên có thể ném MyBusinessException và phương thức doEvenMore có thể ném NumberFormatException.
Trong bước tiếp theo, bạn có thể định nghĩa một khối catch cho mỗi lớp ngoại lệ bạn muốn xử lý và một khối finally.
Tất cả các ngoại lệ được kiểm tra không được xử lý bởi bất kỳ khối catch nào cần phải được chỉ định.
Ví dụ về Khối Catch trong Java Exception
Bạn có thể thực hiện xử lý cho một hoặc nhiều loại ngoại lệ trong khối catch.
Như bạn có thể thấy trong đoạn mã sau, mệnh đề catch lấy ngoại lệ làm tham số. Bạn có thể tham chiếu nó trong khối catch theo tên tham số.
public void performBusinessOperation() {
try {
doSomething("A message");
doSomethingElse();
doEvenMore();
} catch (MyBusinessException e) {
e.printStackTrace();
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
Đoạn mã trên cho thấy hai khối catch. Một để xử lý MyBusinessException và một để xử lý NumberFormatException.
Cả hai khối xử lý các ngoại lệ theo cùng một cách. Kể từ Java 7, bạn có thể làm tương tự chỉ với một khối catch.
public void performBusinessOperation() {
try {
doSomething("A message");
doSomethingElse();
doEvenMore();
} catch (MyBusinessException|NumberFormatException e) {
e.printStackTrace();
}
}
Việc thực hiện các khối catch trong các ví dụ trước là rất cơ bản. Tôi chỉ gọi phương thức printStackTrace để viết lớp, tin nhắn và ngăn xếp cuộc gọi của ngoại lệ cho hệ thống.
com.stackify.example.MyBusinessException: A message that describes the error.
at com.stackify.example.TestExceptionHandling.doSomething(TestExceptionHandling.java:84)
at com.stackify.example.TestExceptionHandling.performBusinessOperation(TestExceptionHandling.java:25)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Trong một ứng dụng thực tế, người ta thường sử dụng cách làm nâng cao hơn.
Ví dụ, bạn có thể hiển thị thông báo lỗi cho người dùng và yêu cầu một đầu vào khác hoặc bạn có thể viết một bản ghi vào nhật ký công việc.
Đôi khi, thậm chí hệ thống vẫn ổn khi bắt và bỏ qua ngoại lệ.
Và trong quá trình làm sản phẩm, bạn cũng cần theo dõi ứng dụng của mình và xử lý ngoại lệ của nó.
Ví dụ về Khối Finally trong Java Exception
Khối finally được thực thi sau khi thực hiện thành công khối try hoặc sau khi một trong các khối catch xử lý một ngoại lệ.
Do đó, đây là một nơi tốt để thực hiện bất kỳ logic nào, như đóng kết nối hoặc InputStream.
Bạn có thể thấy một ví dụ về thao tác dọn dẹp như vậy trong đoạn mã sau.
Khối finally sẽ được thực thi, ngay cả khi việc khởi tạo FileInputStream ném FileNotFoundException hoặc xử lý nội dung tệp sẽ ném bất kỳ ngoại lệ nào khác.
FileInputStream inputStream = null;
try {
File file = new File("./tmp.txt");
inputStream = new FileInputStream(file);
// use the inputStream to read a file
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Như bạn đã thấy, khối finally cung cấp giải pháp để ngăn chặn tất cả sai sót. Và trước phiên bản Java 7, đây là một cách tốt nhất để đưa tất cả giải pháp dọn dẹp, xử lý vào một khối finally.
Try-With-Resource
Cách xử lý ngoại lệ trong Java đã thay đổi từ khi Java 7 giới thiệu câu lệnh try-with-resource.
Nó tự động đóng tất cả các tài nguyên bằng AutoClosizable interface. Và đó là trường hợp của hầu hết các đối tượng Java mà bạn cần đóng.
Điều duy nhất bạn cần làm để sử dụng tính năng này là khởi tạo đối tượng trong mệnh đề try. Bạn cũng cần xử lý hoặc chỉ định tất cả các ngoại lệ có thể bị ném trong khi đóng tài nguyên.
Đoạn mã sau đây cho thấy ví dụ trước với câu lệnh try-with-resource thay vì câu lệnh try-catch-finally
File file = new File("./tmp.txt");
try (FileInputStream inputStream = new FileInputStream(file);) {
// use the inputStream to read a file
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Như bạn có thể thấy, câu lệnh try-with-resource dễ thực hiện và đọc hơn rất nhiều.
Việc xử lý IOException, có thể bị ném trong khi đóng FileInputStream, không yêu cầu một câu lệnh try catch lồng nhau. Bây giờ nó được xử lý bởi một khối catch của câu lệnh try-with-resource
Cách chỉ định ngoại lệ trong Java
Nếu bạn không xử lý một ngoại lệ trong một phương thức, nó sẽ được truyền trong ngăn xếp cuộc gọi.
Và nếu nó là một ngoại lệ được kiểm tra, bạn cũng cần xác định rằng phương thức đó có thể ném ngoại lệ đó.
Bạn có thể làm điều đó bằng cách thêm một mệnh đề throw vào khai báo phương thức. Do đó, tất cả các phương thức gọi cần phải tự xử lý hoặc chỉ định ngoại lệ.
Nếu bạn muốn chỉ ra rằng một phương thức có thể đưa ra một ngoại lệ không được kiểm tra, bạn cũng có thể chỉ định điều này.
public void doSomething(String input) throws MyBusinessException {
// do something useful ...
// if it fails
throw new MyBusinessException("A message that describes the error.");
}
Nên Xử lý hay Chỉ định một ngoại lệ?
Như thường lệ, tùy thuộc vào từng trường hợp sử dụng bạn nên xử lý hay chỉ định một ngoại lệ.
Nói chung, để biết được nên Chỉ định hay là Xử lý bạn cần phải tự hỏi mình những câu hỏi sau đây:
-
Bạn có thể xử lý ngoại lệ trong phương thức hiện tại của bạn không?
-
Bạn có thể dự đoán nhu cầu của tất cả người dùng trong lớp của bạn? Và sẽ xử lý ngoại lệ đáp ứng những nhu cầu này?
Nếu cả 2 câu trả lời đều là Có, bạn nên xử lý ngoại lệ trong phương thức hiện tại của mình.
Trong tất cả các tình huống khác, rất có thể sẽ tốt hơn nếu bạn để chỉ định nó. Điều này cho phép caller của lớp của bạn thực hiện việc xử lý vì nó phù hợp với trường hợp sử dụng hiện tại.
Tổng kết
Trên đây là tất cả về ngoại lệ Java (Exception trong Java) và Cách xử lý ngoại lệ trong Java. Như bạn đã thấy Java cung cấp cho bạn hai kiểu ngoại lệ: Ngoại lệ được kiểm tra và Ngoại lệ không được kiểm tra.
-
Bạn nên sử dụng một ngoại lệ được kiểm tra cho tất cả các sự kiện đặc biệt có thể được ứng dụng mong đợi và xử lý.
-
Bạn cần phải quyết định xem bạn muốn xử lý nó trong một phương thức hay chỉ cần chỉ định nó.
-
Bạn có thể xử lý nó với một khối try - catch - finally hoặc một khối try - with - resource.
Nếu bạn quyết định chỉ định ngoại lệ, nó sẽ trở thành một phần của định nghĩa phương thức và ngoại lệ cần được chỉ định hoặc xử lý bởi tất cả các phương thức gọi.
Bạn nên sử dụng một ngoại lệ không được kiểm tra đối với các lỗi nội bộ không thể dự đoán được.
Bạn không bắt buộc phải xử lý hoặc chỉ định loại ngoại lệ này, nhưng bạn có thể thực hiện theo cách tương tự như khi bạn xử lý hoặc chỉ định một ngoại lệ được kiểm tra.
---
HỌC VIỆN ĐÀO TẠO CNTT NIIT - ICT HÀ NỘI
Dạy học Lập trình chất lượng cao (Since 2002). Học làm Lập trình viên. Hành động ngay!
Đc: Tầng 3, 25T2, N05, Nguyễn Thị Thập, Cầu Giấy, Hà Nội
SĐT: 02435574074 - 0914939543 - 0353655150
Email: hello@niithanoi.edu.vn
Fanpage: https://facebook.com/NIIT.ICT/
#niit #niithanoi #niiticthanoi #hoclaptrinh #khoahoclaptrinh #hoclaptrinhjava #hoclaptrinhphp