Nguồn: Medium Hướng dẫn này mô tả lợi ích của việc thử với tài nguyên so với thử bắt cuối cùng. Bạn cũng sẽ tìm hiểu những điều kiện nào sẽ đưa ra các ngoại lệ bị loại bỏ và cách sử dụng thử với các tài nguyên với nhiều tài nguyên.
Cấu trúc thử với tài nguyên , còn được gọi là thử với tài nguyên , là một cơ chế xử lý ngoại lệ trong Java có thể tự động đóng các tài nguyên như Java inputStream hoặc JDBC Connection khi nó làm việc xong với chúng. Vào năm 2011, Oracle đã thêm thử tài nguyên vào cú pháp ngôn ngữ Java để đảm bảo rằng các đối tượng như ổ cắm mạng, kết nối cơ sở dữ liệu cũng như liên kết tệp và thư mục được đóng lại một cách duyên dáng sau khi chúng được sử dụng. Việc không đóng các tài nguyên này sau khi nhà phát triển mở tay cầm cho chúng có thể dẫn đến rò rỉ bộ nhớ, kích hoạt các quy trình thu gom rác có thể phòng ngừa được và chi phí CPU trên máy chủ.

Trước Java 7
Trong Java, nếu bạn sử dụng các tài nguyên như luồng đầu vào/đầu ra, bạn luôn cần đóng chúng sau khi sử dụng. Vì chúng cũng có thể ném ra các ngoại lệ nên chúng phải được chứa trong khối try-catch . Việc đóng phải xảy ra trong khối cuối cùng . Ít nhất điều này đã đúng cho đến Java 7. Nhưng nó có một số nhược điểm:- Bạn cần kiểm tra xem tài nguyên của bạn có rỗng hay không trước khi đóng nó.
- Bản thân việc đóng có thể đưa ra các ngoại lệ, vì vậy bạn phải có một lần thử khác trong khối cuối cùng của mình .
- Các lập trình viên có xu hướng quên đóng tài nguyên của họ.
Làm cách nào để sử dụng câu lệnh try-with-resource?
Toán tử này ban đầu được giới thiệu trong Java 7 và ý tưởng là các nhà phát triển không còn cần phải lo lắng về việc quản lý tài nguyên mà họ sử dụng trong khối try-catch-finally nữa . Điều này đạt được bằng cách loại bỏ sự cần thiết của các khối cuối cùng , trong thực tế chỉ được các nhà phát triển sử dụng để đóng tài nguyên. Trong Java, câu lệnh try-with-resources là câu lệnh try khai báo một hoặc nhiều tài nguyên. Tài nguyên là một đối tượng phải được đóng sau khi chương trình kết thúc. Khi việc thực thi mã rời khỏi khối try-with-resources thì mọi tài nguyên được mở trong khối try-with-resources sẽ tự động bị đóng, bất kể có ngoại lệ nào được đưa ra trong khối try-with-resources hay trong khi cố gắng đóng tài nguyên hay không . Để sử dụng tính năng ngôn ngữ try-with-resources của Java , các quy tắc sau sẽ được áp dụng:- Tất cả các đối tượng được điều khiển bởi câu lệnh try-with-resources phải triển khai giao diện AutoClosizable .
- Nhiều đối tượng AutoCloseable có thể được tạo trong khối try-with-resources .
- Các đối tượng được khai báo trong câu lệnh try-with-resources được thực thi trong khối try chứ không phải trong khối Catch hoặc khối cuối cùng .
- Phương thức close() của các đối tượng được khai báo trong khối try-with-resources được gọi bất kể ngoại lệ có được ném ra trong thời gian chạy hay không.
- Nếu một ngoại lệ được đưa ra trong phương thức close() , nó có thể được phân loại là một ngoại lệ bị loại bỏ.
Cú pháp
try(declare resources here) {
// использовать ресурсы
}
catch(FileNotFoundException e) {
// обработка исключений
}
Sử dụng thực tế try-with-resource
Để tự động đóng, tài nguyên phải được khai báo và khởi tạo bên trong try :try (PrintWriter writer = new PrintWriter(new File("test.txt"))) {
writer.println("Hello World");
}
Thay thế try-catch-finally bằng try-with-resources
Một cách đơn giản và rõ ràng để sử dụng chức năng thử với tài nguyên là thay thế khối thử-bắt-cuối cùng dài dòng và truyền thống . Hãy so sánh các ví dụ mã sau đây. Ví dụ đầu tiên là khối try-catch-finally điển hình :Scanner scanner = null;
try {
scanner = new Scanner(new File("test.txt"));
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (scanner != null) {
scanner.close();
}
}
Và đây là một giải pháp siêu ngắn gọn mới sử dụng try-with-resources :
try (Scanner scanner = new Scanner(new File("test.txt"))) {
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
}
Sự khác biệt giữa thử và thử với tài nguyên
Khi nói đến các trường hợp ngoại lệ, có sự khác biệt giữa khối try-catch-finally và khối try-with-resources . Ngoại lệ được ném vào cả khối try và khối cuối cùng , tuy nhiên phương thức này chỉ trả về ngoại lệ được ném trong khối cuối cùng . Đối với try-with-resources , nếu một ngoại lệ xảy ra trong khối try và trong câu lệnh try-with-resources , phương thức sẽ trả về ngoại lệ được ném vào khối try . Các ngoại lệ do khối try-with-resources ném ra bị loại bỏ, nghĩa là chúng ta có thể nói rằng khối try-with-resources ném ra các ngoại lệ bị loại bỏ.Tại sao tôi nên sử dụng câu lệnh try-with-resource?
Câu lệnh try-with-resource đảm bảo rằng mỗi tài nguyên được đóng ở cuối câu lệnh. Nếu chúng tôi không đóng tài nguyên, điều này có thể dẫn đến rò rỉ tài nguyên và chương trình có thể cạn kiệt tài nguyên hiện có. Điều này xảy ra khi bạn sử dụng khối try-catch-finally . Trước Java SE 7, bạn có thể sử dụng khối cuối cùng để đảm bảo rằng tài nguyên sẽ bị đóng bất kể câu lệnh try thoát bình thường hay đột ngột. Ví dụ sau sử dụng khối cuối cùng thay vì câu lệnh try-with-resources :static String readFirstLineFromFileWithFinallyBlock(String path) throws IOException {
FileReader fr = new FileReader(path);
BufferedReader br = new BufferedReader(fr);
try {
return br.readLine();
} finally {
br.close();
fr.close();
}
}
Có thể có rò rỉ tài nguyên trong ví dụ này. Chương trình phải làm nhiều việc hơn là dựa vào trình thu gom rác để giải phóng bộ nhớ của tài nguyên sau khi sử dụng xong. Chương trình cũng phải trả lại tài nguyên cho hệ điều hành, thường bằng cách gọi phương thức đóng của tài nguyên. Tuy nhiên, nếu chương trình không thực hiện việc này trước khi trình thu gom rác trả lại tài nguyên thì thông tin cần thiết để giải phóng tài nguyên sẽ bị mất. Một tài nguyên bị rò rỉ mà hệ điều hành vẫn xem xét sử dụng. Trong ví dụ hiển thị ở trên, nếu phương thức readLine ném ra một ngoại lệ và câu lệnh br.close() trong khối cuối cùng ném ra một ngoại lệ thì FileReader đã bị rò rỉ . Đây là lý do tại sao bạn nên sử dụng câu lệnh try-with-resources thay vì khối cuối cùng để đóng tài nguyên chương trình của mình.
Một ví dụ nữa
Ví dụ sau đọc dòng đầu tiên từ một tập tin. Các phiên bản FileReader và BufferedReader được sử dụng để đọc dữ liệu . Đây là những tài nguyên cần được đóng sau khi chương trình thoát.import java.io.*;
class Main {
public static void main(String[] args) {
String line;
try(BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
while ((line = br.readLine()) != null) {
System.out.println("Line =>"+line);
}
} catch (IOException e) {
System.out.println("IOException in try block =>" + e.getMessage());
}
}
}
Như bạn có thể thấy, tài nguyên được khai báo trong câu lệnh try-with-resources là BufferedReader . Các câu lệnh khai báo cho tài nguyên này xuất hiện trong ngoặc đơn ngay sau từ khóa try . Các lớp BufferedReader trong Java SE 7 trở lên triển khai giao diện java.lang.AutoCloseable . Vì các phiên bản BufferedReader được khai báo trong câu lệnh try-with-resource nên chúng sẽ bị đóng bất kể câu lệnh try thoát bình thường hay đột ngột (nếu phương thức BufferedReader.readLine() ném ra IOException ).
Ngoại lệ bị loại bỏ
Nếu khối try ném ra một ngoại lệ và một hoặc nhiều ngoại lệ xuất hiện trong khối try-with-resources thì các ngoại lệ do khối try-with-resources ném ra sẽ bị loại bỏ. Nói cách khác, chúng ta có thể nói rằng các ngoại lệ do try-with-resource đưa ra là các ngoại lệ bị loại bỏ. Bạn có thể phát hiện những ngoại lệ này bằng phương thức getSuppress() của lớp Throwable . Trong ví dụ được hiển thị trước đó, các ngoại lệ được đưa ra bởi câu lệnh try-with-resources , theo các điều kiện sau:- Không tìm thấy tệp test.txt .
- Đóng đối tượng BufferedReader .
Bắt các ngoại lệ bị loại bỏ
Trong Java 7 trở lên, có thể lấy được các ngoại lệ bị loại bỏ bằng cách gọi phương thức Throwable.getSuppression() từ một ngoại lệ do khối thử ném ra . getSuppress() trả về một mảng chứa tất cả các ngoại lệ đã bị câu lệnh try-with-resources loại bỏ . Nếu không có ngoại lệ nào bị loại bỏ hoặc việc loại bỏ bị vô hiệu hóa thì một mảng trống sẽ được trả về. Đây là một ví dụ về việc nhận các ngoại lệ bị chặn trong khối bắt :catch(IOException e) {
System.out.println("Thrown exception=>" + e.getMessage());
Throwable[] suppressedExceptions = e.getSuppressed();
for (int i=0; i" + suppressedExceptions[i]);
}
}
Lợi ích của việc sử dụng tài nguyên dùng thử
- Dễ đọc và dễ viết mã. Quản lý tài nguyên tự động.
- Số lượng dòng mã đã được giảm bớt.
- Khi nhiều tài nguyên được mở trong try-with-resource , chúng sẽ được đóng theo thứ tự ngược lại để tránh các vấn đề phụ thuộc.
import java.io.*;
class Main {
public static void main(String[] args) {
BufferedReader br = null;
String line;
try {
System.out.println("Entering try block");
br = new BufferedReader(new FileReader("test.txt"));
while ((line = br.readLine()) != null) {
System.out.println("Line =>"+line);
}
} catch (IOException e) {
System.out.println("IOException in try block =>" + e.getMessage());
} finally {
System.out.println("Entering finally block");
try {
if (br != null) {
br.close();
}
} catch (IOException e) {
System.out.println("IOException in finally block =>"+e.getMessage());
}
}
}
}
Phần kết luận:
Nhập khối thử Dòng => dòng từ tệp test.txt Nhập khối cuối cùng
Như bạn có thể thấy từ ví dụ trên, việc sử dụng khối cuối cùng để dọn sạch tài nguyên sẽ làm tăng độ phức tạp của mã. Chú ý khối try...catch trong khối cuối cùng ? Điều này là do IOException cũng có thể xảy ra khi đóng một phiên bản BufferedReader bên trong khối cuối cùng , do đó nó cũng bị bắt và xử lý. Câu lệnh try-with-resource thực hiện quản lý tài nguyên tự động. Chúng ta không cần phải đóng tài nguyên một cách rõ ràng vì JVM sẽ tự động đóng chúng. Điều này làm cho mã dễ đọc hơn và dễ viết hơn.
Thử với nhiều tài nguyên với nhiều tài nguyên
Chúng ta có thể khai báo nhiều tài nguyên trong khối try-with-resource bằng cách phân tách chúng bằng dấu chấm phẩy:try (Scanner scanner = new Scanner(new File("testRead.txt"));
PrintWriter writer = new PrintWriter(new File("testWrite.txt"))) {
while (scanner.hasNext()) {
writer.print(scanner.nextLine());
}
}
Java 9 - Biến cuối cùng hiệu quả
Trước Java 9, chúng ta chỉ có thể sử dụng các biến mới bên trong khối try-with-resources :try (Scanner scanner = new Scanner(new File("testRead.txt"));
PrintWriter writer = new PrintWriter(new File("testWrite.txt"))) {
// omitted
}
Lưu ý rằng điều này khá dài dòng khi khai báo nhiều tài nguyên. Kể từ Java 9 (bản cập nhật JEP 213), chúng ta có thể sử dụng các biến cuối cùng hoặc thậm chí một cách hiệu quả cuối cùng bên trong khối try-with-resources :
final Scanner scanner = new Scanner(new File("testRead.txt"));
PrintWriter writer = new PrintWriter(new File("testWrite.txt"))
try (scanner;writer) {
// omitted
}
Nói một cách đơn giản, một biến thực sự là cuối cùng nếu nó không được sửa đổi sau lần gán đầu tiên, ngay cả khi nó không được đánh dấu rõ ràng là cuối cùng . Như được hiển thị ở trên, biến scanner được khai báo rõ ràng là cuối cùng để chúng ta có thể sử dụng nó với khối try-with-resources . Mặc dù biến nhà văn không rõ ràng là cuối cùng nhưng nó không thay đổi sau lần gán đầu tiên. Vì vậy chúng ta cũng có thể sử dụng biến writer . Tôi hy vọng hôm nay bạn đã hiểu rõ hơn về cách xử lý các trường hợp ngoại lệ bằng cách sử dụng câu lệnh try-with-resource . Chúc bạn học tập vui vẻ!
GO TO FULL VERSION