1. Nguồn lực bên ngoài

Khi một chương trình Java chạy, đôi khi nó tương tác với các thực thể bên ngoài máy Java. Ví dụ, với các tập tin trên đĩa. Các thực thể này thường được gọi là tài nguyên bên ngoài. Tài nguyên bên trong là các đối tượng được tạo bên trong máy Java.

Thông thường, sự tương tác tuân theo sơ đồ này:

Tuyên bố dùng thử tài nguyên

tài nguyên theo dõi

Hệ điều hành theo dõi chặt chẽ các tài nguyên có sẵn và cũng kiểm soát quyền truy cập được chia sẻ vào chúng từ các chương trình khác nhau. Ví dụ: nếu một chương trình thay đổi tệp, thì chương trình khác không thể thay đổi (hoặc xóa) tệp đó. Nguyên tắc này không giới hạn đối với các tệp, nhưng chúng cung cấp ví dụ dễ hiểu nhất.

Hệ điều hành có các chức năng (API) cho phép chương trình lấy và/hoặc giải phóng tài nguyên. Nếu một tài nguyên đang bận, thì chỉ chương trình đã mua nó mới có thể làm việc với nó. Nếu một tài nguyên là miễn phí, thì bất kỳ chương trình nào cũng có thể có được nó.

Hãy tưởng tượng rằng văn phòng của bạn đã chia sẻ cốc cà phê. Nếu ai đó lấy một cái cốc, thì những người khác không thể lấy nó nữa. Nhưng một khi chiếc cốc được sử dụng, rửa sạch và đặt lại vào vị trí của nó, thì bất kỳ ai cũng có thể lấy lại. Tình hình với chỗ ngồi trên xe buýt hoặc tàu điện ngầm cũng vậy. Nếu một chỗ ngồi miễn phí, thì bất cứ ai cũng có thể lấy nó. Nếu một ghế đã có người ngồi, thì nó sẽ do người ngồi đó kiểm soát.

Thu nhận các nguồn lực bên ngoài .

Mỗi khi chương trình Java của bạn bắt đầu làm việc với một tệp trên đĩa, máy Java sẽ yêu cầu hệ điều hành cấp quyền truy cập độc quyền vào tệp đó. Nếu tài nguyên là miễn phí, thì máy Java sẽ lấy nó.

Nhưng sau khi bạn làm việc xong với tệp, tài nguyên (tệp) này phải được giải phóng, tức là bạn cần thông báo cho hệ điều hành rằng bạn không cần nó nữa. Nếu bạn không làm điều này, tài nguyên sẽ tiếp tục được giữ bởi chương trình của bạn.

Hệ điều hành duy trì một danh sách các tài nguyên bị chiếm giữ bởi mỗi chương trình đang chạy. Nếu chương trình của bạn vượt quá giới hạn tài nguyên được chỉ định, thì hệ điều hành sẽ không cung cấp cho bạn tài nguyên mới nữa.

Tin vui là nếu chương trình của bạn kết thúc, tất cả tài nguyên sẽ tự động được giải phóng (chính hệ điều hành sẽ thực hiện việc này).

Tin xấu là nếu bạn đang viết một ứng dụng máy chủ (và rất nhiều ứng dụng máy chủ được viết bằng Java), thì máy chủ của bạn cần có khả năng chạy liên tục trong nhiều ngày, nhiều tuần và nhiều tháng. Và nếu bạn mở 100 tệp mỗi ngày và không đóng chúng, thì sau vài tuần, ứng dụng của bạn sẽ đạt đến giới hạn tài nguyên và gặp sự cố. Điều đó còn thua xa những tháng làm việc ổn định.


2. close()phương pháp

Các lớp sử dụng tài nguyên bên ngoài có một phương pháp đặc biệt để giải phóng chúng: close().

Dưới đây chúng tôi cung cấp một ví dụ về một chương trình viết một cái gì đó vào một tệp và sau đó đóng tệp khi nó được thực hiện xong, tức là nó giải phóng tài nguyên của hệ điều hành. Nó trông giống như thế này:

Mã số Ghi chú
String path = "c:\\projects\\log.txt";
FileOutputStream output = new FileOutputStream(path);
output.write(1);
output.close();
Đường dẫn đến tệp .
Lấy đối tượng tệp: lấy tài nguyên.
Ghi vào tệp
Đóng tệp - giải phóng tài nguyên

Sau khi làm việc với một tệp (hoặc các tài nguyên bên ngoài khác), bạn phải gọi phương close()thức trên đối tượng được liên kết với tài nguyên bên ngoài.

ngoại lệ

Tất cả có vẻ đơn giản. Nhưng các ngoại lệ có thể xảy ra khi chương trình chạy và tài nguyên bên ngoài sẽ không được giải phóng. Và điều đó rất tệ.

Để đảm bảo rằng close()phương thức luôn được gọi, chúng ta cần bọc mã của mình trong một khối try- catch- finallyvà thêm close()phương thức vào finallykhối. Nó sẽ trông giống như thế này:

try
{
   FileOutputStream output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

Mã này sẽ không được biên dịch vì outputbiến được khai báo bên trong try {}khối và do đó không hiển thị trong finallykhối.

Hãy sửa nó:

FileOutputStream output = new FileOutputStream(path);

try
{
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

Không sao, nhưng nó sẽ không hoạt động nếu xảy ra lỗi khi chúng ta tạo đối FileOutputStreamtượng và điều này có thể xảy ra khá dễ dàng.

Hãy sửa nó:

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

Vẫn còn một vài lời chỉ trích. Đầu tiên, nếu xảy ra lỗi khi tạo FileOutputStreamđối tượng, thì outputbiến đó sẽ là null. Khả năng này phải được tính đến trong finallykhối.

Thứ hai, close()phương thức luôn được gọi trong finallykhối, có nghĩa là nó không cần thiết trong trykhối. Mã cuối cùng sẽ trông như thế này:

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   if (output != null)
      output.close();
}

Ngay cả khi chúng tôi không xem xét catchkhối, khối này có thể bị bỏ qua, thì 3 dòng mã của chúng tôi sẽ trở thành 10. Nhưng về cơ bản, chúng tôi chỉ mở tệp và viết 1. Hơi rườm rà, bạn có nghĩ vậy không?


3. try-với-tài nguyên

Và tại đây, những người tạo ra Java đã quyết định rắc một chút đường cú pháp cho chúng tôi. Bắt đầu với phiên bản thứ 7, Java có trycâu lệnh -with-resources mới.

Nó được tạo ra chính xác để giải quyết vấn đề với lệnh gọi phương close()thức bắt buộc. Trường hợp chung trông khá đơn giản:

try (ClassName name = new ClassName())
{
     Code that works with the name variable
}

Đây là một biến thể khác của try tuyên bố . Bạn cần thêm dấu ngoặc đơn sau trytừ khóa, sau đó tạo đối tượng có tài nguyên bên ngoài bên trong dấu ngoặc đơn. Đối với mỗi đối tượng trong dấu ngoặc đơn, trình biên dịch thêm một finallyphần và lời gọi phương close()thức.

Dưới đây là hai ví dụ tương đương:

mã dài Mã với tài nguyên dùng thử
FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
finally
{
   if (output != null)
   output.close();
}
try(FileOutputStream output = new FileOutputStream(path))
{
   output.write(1);
}

Mã sử ​​dụng try-with-resources ngắn hơn và dễ đọc hơn nhiều. Và chúng ta càng có ít mã thì càng ít có khả năng mắc lỗi đánh máy hoặc lỗi khác.

Nhân tiện, chúng ta có thể thêm catchfinallychặn vào trycâu lệnh -with-resources. Hoặc bạn không thể thêm chúng nếu không cần thiết.



4. Nhiều biến cùng một lúc

Nhân tiện, bạn có thể thường gặp phải tình huống cần mở nhiều tệp cùng một lúc. Giả sử bạn đang sao chép một tệp, vì vậy bạn cần hai đối tượng: tệp mà bạn đang sao chép dữ liệu và tệp mà bạn đang sao chép dữ liệu.

Trong trường hợp này, trycâu lệnh -with-resources cho phép bạn tạo một nhưng nhiều đối tượng trong đó. Mã tạo các đối tượng phải được phân tách bằng dấu chấm phẩy. Đây là sự xuất hiện chung của tuyên bố này:

try (ClassName name = new ClassName(); ClassName2 name2 = new ClassName2())
{
   Code that works with the name and name2 variables
}

Ví dụ sao chép tập tin:

mã dài Mã ngắn
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

FileInputStream input = null;
FileOutputStream output = null;

try
{
   input = new FileInputStream(src);
   output = new FileOutputStream(dest);

   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}
finally
{
   if (input != null)
      input.close();
   if (output != null)
      output.close();
}
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);

FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}

Vâng, những gì chúng ta có thể nói ở đây? try-với-tài nguyên là một điều tuyệt vời!


5. AutoCloseablegiao diện

Nhưng đó không phải là tất cả. Người đọc chăm chú sẽ ngay lập tức bắt đầu tìm kiếm những cạm bẫy hạn chế cách áp dụng tuyên bố này.

Nhưng trycâu lệnh -with-resources hoạt động như thế nào nếu lớp không có close()phương thức? Chà, giả sử rằng sẽ không có gì được gọi. Không có phương pháp, không có vấn đề.

Nhưng trycâu lệnh -with-resources hoạt động như thế nào nếu lớp có nhiều close()phương thức? Và họ cần lập luận để được chuyển cho họ? Và lớp không có close()phương thức không có tham số?

Tôi hy vọng bạn đã thực sự tự hỏi mình những câu hỏi này, và có lẽ còn những câu hỏi khác nữa.

Để tránh những vấn đề như vậy, những người tạo ra Java đã đưa ra một giao diện đặc biệt gọi là AutoCloseable, chỉ có một phương thức — close(), không có tham số.

Họ cũng đã thêm hạn chế rằng chỉ các đối tượng của các lớp triển khai mớiAutoCloseable có thể được khai báo là tài nguyên trong trycâu lệnh -with-resources. Kết quả là, các đối tượng như vậy sẽ luôn có một close()phương thức không có tham số.

Nhân tiện, bạn có nghĩ rằng trycâu lệnh -with-resources có thể khai báo dưới dạng tài nguyên đối tượng có lớp có close()phương thức riêng không có tham số nhưng không triển khai không AutoCloseable?

Tin xấu: Câu trả lời đúng là không — các lớp phải triển khai AutoCloseablegiao diện.

Tin tốt là: Java có rất nhiều lớp triển khai giao diện này, vì vậy rất có thể mọi thứ sẽ hoạt động bình thường.