Khi ngoại lệ xảy ra, chương trình sẽ bị gián đoạn, đôi khi dẫn đến việc kết thúc đột ngột. Việc hiểu và xử lý ngoại lệ là quan trọng để đảm bảo ổn định của ứng dụng.
Ngoại lệ trong Java
Có nhiều nguyên nhân gây ngoại lệ trong Java, bao gồm:
- Người dùng nhập liệu không đúng.
- Không tìm thấy file cần sử dụng.
- Mất kết nối mạng trong quá trình giao tiếp hoặc JVM hết bộ nhớ.
Một số ngoại lệ xảy ra có thể là do lỗi của người dùng, một số do lỗi của lập trình viên và có những trường hợp là do lỗi từ nguồn dữ liệu vật lý.
Dựa vào các nguyên nhân trên, chúng ta có 3 loại ngoại lệ khác nhau. Việc nắm bắt cách xử lý chúng là quan trọng khi làm việc với Java.
- Ngoại lệ kiểm tra (Checked exception): Xảy ra trong quá trình biên dịch, chúng còn được gọi là ngoại lệ thời gian biên dịch. Những ngoại lệ này không thể bỏ qua dễ dàng ở thời điểm biên dịch, do đó, lập trình viên cần chú ý đến việc xử lý chúng.
Ví dụ, khi sử dụng lớp FileReader để đọc dữ liệu từ một tập tin, nếu tập tin được chỉ định trong constructor không tồn tại, sẽ xảy ra FileNotFoundException và trình biên dịch sẽ yêu cầu lập trình viên xử lý ngoại lệ này.
Ví dụ:
Khi cố gắng biên dịch chương trình trên, bạn sẽ nhận được ngoại lệ như sau:
C:\>javac FilenotFound_Demo.java
FilenotFound_Demo.java:8: lỗi: FileNotFoundException chưa được báo cáo; phải được bắt hoặc được khai báo để ném
FileReader fr = new FileReader(file);
^
1 lỗi
Lưu ý: Vì các phương thức read() và close() của lớp FileReader ném IOException, bạn có thể quan sát các thông báo trình biên dịch để xử lý IOException, cùng FileNotFoundException.
- Unchecked exception: Là ngoại lệ xảy ra vào thời điểm thực thi, nó còn được gọi là Runtime Exception. Ngoại lệ này bao gồm các lỗi lập trình, ví dụ như lỗi logic hoặc lỗi sử dụng API không đúng cách. Runtime Exception được bỏ qua tại thời điểm biên dịch.
Ví dụ nếu khai báo mảng 5 phần tử trong chương trình và bạn cố gắng gọi phần tử thứ 6 của mảng, thì ngoại lệ ArrayIndexOutOfBoundsExceptionexception sẽ xảy ra.
Ví dụ:
Nếu biên dịch và thực thi chương trình trên, bạn sẽ gặp phải tình trạng sau:
Ngoại lệ trong luồng 'main' java.lang.ArrayIndexOutOfBoundsException: 5
tại Exceptions.Unchecked_Demo.main(Unchecked_Demo.java:8)
- Lỗi: Không giống với các ngoại lệ, lỗi đại diện cho các vấn đề phát sinh vượt quá khả năng kiểm soát của người dùng hoặc lập trình viên. Lỗi được bỏ qua trong mã vì hiếm khi bạn có thể làm gì đó khi chương trình gặp lỗi. Ví dụ, nếu lỗi tràn bộ đệm xảy ra, chương trình sẽ bị lỗi. Lỗi cũng bị bỏ qua tại thời điểm biên dịch.
Bậc thang ngoại lệ (exception) trong Java
Tất cả các lớp ngoại lệ là lớp con của lớp java.lang.Exception. Lớp ngoại lệ là lớp con của lớp Throwable. Một dạng lớp ngoại lệ khác là Lỗi, cũng là lớp con của lớp Throwable.
Lỗi biểu diễn cho các điều kiện bất thường xảy ra trong trường hợp lỗi nghiêm trọng, không thể được các chương trình Java xử lý. Lỗi được tạo ra để biểu diễn lỗi xảy ra trong môi trường chạy. Ví dụ: JVM đã hết bộ nhớ. Thông thường, các chương trình không thể phục hồi từ các lỗi này.
Lớp ngoại lệ chia thành 2 lớp con chính: IOException và RuntimeException.
Dưới đây là danh sách các ngoại lệ Checked exception và Unchecked exception có sẵn trong Java:
Các phương thức trong lớp ngoại lệ của Java
Dưới đây là bảng danh sách các phương thức quan trọng có sẵn trong lớp Throwable của Java:
Xử lý ngoại lệ trong Java
Bộ xử lý catch trong ngoại lệ
Phương thức catch là một cơ chế sử dụng kết hợp từ khóa try và catch. Một khối try / catch bao quanh mã có thể tạo ra ngoại lệ. Mã bên trong khối try / catch được gọi là mã được bảo vệ. Dưới đây là cú pháp để sử dụng khối try / catch:
Cú pháp
Thử {
// Mã bảo vệ
} bắt (ExceptionName e1) {
// Khối bắt
}
Mã có thể gây ngoại lệ được đặt trong khối try. Khi có ngoại lệ, khối catch liên kết sẽ xử lý. Mỗi khối try đều có thể được theo sau bởi một khối catch hoặc khối finally.
Một khối catch khai báo loại ngoại lệ mà bạn muốn xử lý. Khi có ngoại lệ, khối catch sẽ được kiểm tra. Nếu loại ngoại lệ khớp với khối catch, ngoại lệ sẽ được chuyển vào khối catch như một tham số.
Ví dụ:
Dưới đây là khai báo mảng với 2 phần tử. Mã cố gắng truy cập phần tử thứ 3 của mảng và gây ra ngoại lệ:
Ví dụ trên sẽ có kết quả đầu ra như sau:
Ngoại lệ xảy ra: java.lang.ArrayIndexOutOfBoundsException: 3
Ngoài khối try-catch
Sử dụng nhiều khối catch trong Java
Một khối try có thể có nhiều khối catch đi kèm.
Cú pháp cho nhiều khối catch được biểu diễn như sau:
Thử nghiệm {
// Mã bảo vệ
} catch (LoạiNgoạiLệ1 e1) {
// Khối catch
} catch (LoạiNgoạiLệ2 e2) {
// Khối catch
} catch (LoạiNgoạiLệ3 e3) {
// Khối catch
}
Các đoạn mã trên thể hiện 3 khối catch, bạn có thể thêm nhiều khối khác sau try. Nếu có lỗi, chúng sẽ được xử lý từ khối catch đầu tiên trong danh sách.
Nếu kiểu dữ liệu của lỗi khớp với ExceptionType1, nó sẽ được xử lý tại đó. Nếu không, lỗi sẽ được chuyển xuống catch thứ 2 và tiếp tục cho đến khi lỗi được xử lý hoặc trải qua tất cả các catch. Trong trường hợp này, phương thức hiện tại sẽ dừng và lỗi sẽ được truyền lên call stack.
Ví dụ:
Dưới đây là đoạn mã minh họa cách sử dụng nhiều lệnh try / catch:
Xử lý nhiều lỗi trong một khối catch
Tính từ Java 7, bạn có thể xử lý nhiều lỗi trong một khối catch đơn giản.
Dưới đây là cú pháp cơ bản:
catch (IOException|FileNotFoundException ex) {
logger.ghiNhớ(ex);
ném ex;
Xử lý lỗi bằng từ khóa Throws/Throw trong Java
Nếu một phương thức không xử lý lỗi checked exception, phương thức đó phải khai báo nó bằng cách sử dụng từ khóa throws. Từ khóa throws nằm cuối chữ ký của phương thức.
Bạn có thể ném ngoại lệ hoặc tạo một lỗi mới hoặc lỗi mà bạn vừa bắt bằng cách sử dụng từ khóa throw.
Giữa từ khóa throws và từ khóa throw có sự khác nhau. Throws được sử dụng để trì hoãn xử lý ngoại lệ checked exception còn throw được sử dụng để gọi ngoại lệ.
Ví dụ phương thức dưới đây khai báo throws RemoteException:
Một phương thức có thể khai báo throws nhiều lỗi hơn, trong trường hợp các ngoại lệ được khai báo trong một danh sách được ngăn cách nhau bởi dấu phẩy.
Ví dụ phương thức dưới đây thông báo về throws RemoteException và InsufficientFundsException:
Khối Cuối cùng
Khối finally theo sau một khối try hoặc một khối catch. Khối finally được thực thi, kể cả trong trường hợp nếu xuất hiện ngoại lệ.
Sử dụng khối finally cho phép bạn chạy các lệnh kiểu dọn dẹp, bất kể điều gì xảy ra trong mã được bảo vệ.
Khối finally đặt ở cuối khối catch.
Cú pháp:
try {
// Mã được bảo vệ
} bắt (ExceptionType1 e1) {
// Khối bắt
} bắt (ExceptionType2 e2) {
// Khối bắt
} bắt (ExceptionType3 e3) {
// Khối bắt
} cuối cùng {
// Khối finally luôn được thực thi.
}
Ví dụ:
Trong ví dụ này, kết quả đầu ra sẽ là:
Ngoại lệ xảy ra: java.lang.ArrayIndexOutOfBoundsException: 3
Giá trị của phần tử đầu tiên: 6
Lệnh finally được thực thi
Chú ý:
- Khối catch không tồn tại nếu không có lệnh try.
- Không bắt buộc phải có khối finally ngay cả khi có khối try / catch.
- Khối try không thể xuất hiện nếu không có khối catch hoặc finally.
- Mã không thể xuất hiện giữa các khối try, catch và finally.
Try-with-resources trong Java
Khi sử dụng các tài nguyên như stream, connection, ... chúng ta cần đóng các tài nguyên này bằng cách sử dụng khối finally.
Trong ví dụ dưới đây, chương trình đọc dữ liệu từ một tập tin bằng cách sử dụng FileReader và tập tin này được đóng tự động bằng khối finally:
Try-with-resources còn được gọi là quản lý tài nguyên tự động, là cơ chế xử lý ngoại lệ mới được giới thiệu từ Java 7, tự động đóng các tài nguyên được sử dụng trong khối try-catch.
Để sử dụng tính năng này, bạn chỉ cần khai báo các tài nguyên cần thiết trong dấu ngoặc, và tài nguyên được tạo sẽ tự động đóng sau khi khối kết thúc. Dưới đây là cú pháp của lệnh try-with-resources:
Cú pháp:
try(FileReader fr = new FileReader('đường dẫn tập tin')) {
// sử dụng tài nguyên
} catch () {
// nội dung của khối catch
}
}
Ví dụ minh họa dưới đây là chương trình đọc dữ liệu từ một tập tin bằng cách sử dụng lệnh try-with-resources:
Một số điểm lưu ý khi sử dụng lệnh try-with-resources:
- Để sử dụng lớp với lệnh try-with-resources, nó phải thực hiện AutoCloseable interface và phương thức close() sẽ tự động được gọi trong runtime.
- Bạn có thể khai báo nhiều lớp trong lệnh try-with-resources.
- Trong khi khai báo nhiều lớp trong khối try của lệnh try-with-resources, các lớp sẽ được đóng theo thứ tự ngược lại.
- Ngoại trừ việc khai báo các tài nguyên trong dấu ngoặc đơn, mọi thứ đều giống trong khối try / catch của một khối try.
- Tài nguyên được khai báo tại khối try được ngầm hiểu là cuối cùng.
Tạo Ngoại lệ do Người dùng Định nghĩa trong Java
Khi bạn muốn tạo các ngoại lệ tự định nghĩa trong Java, hãy lưu ý một số điểm quan trọng sau đây:
- Tất cả các ngoại lệ phải kế thừa từ lớp Throwable.
- Nếu bạn muốn tạo một ngoại lệ kiểu checked exception, mà có thể được xử lý tự động theo nguyên tắc Handle hoặc Declare, bạn cần phải mở rộng lớp Exception.
- Nếu bạn muốn tạo một ngoại lệ kiểu runtime exception, bạn cần phải mở rộng lớp RuntimeException.
Để định nghĩa một lớp ngoại lệ, bạn có thể sử dụng cú pháp sau:
class NgoạiLệTựĐịnhNghĩa extends Exception {
}
Việc tạo một ngoại lệ riêng chỉ đơn giản là mở rộng từ lớp ngoại lệ được định trước. Các ngoại lệ này được coi là kiểu ngoại lệ checked exception. Ví dụ về lớp InsufficientFundsException dưới đây là một ngoại lệ do người dùng định nghĩa, mở rộng từ lớp Exception.
Lớp ngoại lệ cũng tương tự như các lớp khác, có thể chứa các trường và phương thức hữu ích.
Ví dụ:
Để minh họa việc sử dụng ngoại lệ do người dùng định nghĩa, lớp CheckingAccount dưới đây thực hiện phương thức withdraw(), ném một ngoại lệ InsufficientFundsException:
Chương trình BankDemo dưới đây biểu diễn phương thức deposit() và withdraw() của CheckingAccount:
Biên dịch tất cả 3 file trên và chạy BankDemo sẽ trả về kết quả như sau:
Gửi $500...
Rút $100...
Rút $600...
Xin lỗi, bạn thiếu $200.0
Ngoại lệKhôngĐủSốDư
tại CheckingAccount.rútTiền(CheckingAccount.java:25)
tại BankDemo.chính(BankDemo.java:13)
Các ngoại lệ phổ biến
Trong Java, có 2 loại ngoại lệ phổ biến: Exception và Error:
- Ngoại lệ của JVM: Đây là các ngoại lệ hoặc lỗi mà JVM tự động đưa ra. Ví dụ: NullPointerException, ArrayIndexOutOfBoundsException, ClassCastException.
- Ngoại lệ theo chương trình: Những ngoại lệ này được ứng dụng hoặc API lập trình tạo ra. Ví dụ: IllegalArgumentException, IllegalStateException.
Trong bài viết này, Mytour giới thiệu về ngoại lệ và cách xử lý chúng trong Java. Để hiểu thêm về ngôn ngữ Java, bạn có thể tham khảo hướng dẫn cơ bản về ngôn ngữ Java. Nếu bạn có thắc mắc, hãy để lại ý kiến của mình trong phần bình luận dưới bài viết.