"Amigo, mười túp lều!"

"Tôi rất vui khi được học Java, Đội trưởng!"

"Thoải mái, Amigo. Hôm nay chúng ta có một chủ đề cực kỳ thú vị. Chúng ta sẽ nói về cách một chương trình Java tương tác với các tài nguyên bên ngoài và chúng ta sẽ nghiên cứu một câu lệnh Java rất thú vị. Tốt hơn là đừng bịt tai lại."

"Ta nghe rõ."

"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 trên đĩa. Những thực thể này thường được gọi là tài nguyên bên ngoài."

"Vậy cái gì gọi là nội lực?"

"Tài nguyên nội bộ là các đối tượng được tạo bên trong máy Java. Thông thường, tương tác tuân theo sơ đồ này:

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

"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 trong 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 tài nguyên đang bận, thì chỉ chương trình đã lấy nó mới có thể hoạt động với nó. Nếu tài nguyên trống, thì bất kỳ chương trình nào cũng có thể lấy Nó.

"Hãy tưởng tượng rằng một văn phòng dùng chung cốc cà phê. Nếu ai đó lấy cốc thì những người khác không thể lấy được nữa. Nhưng một khi cốc được sử dụng, rửa sạch và đặt trở lại vị trí của nó, thì bất kỳ ai cũng có thể lấy lại."

"Hiểu rồi. Nó giống như chỗ ngồi trên tàu điện ngầm hoặc các phương tiện giao thông công cộng khác. Ghế trống thì ai cũng có thể ngồi được. Ghế có người ngồi thì người ngồi đó sẽ bị khống chế."

"Đúng vậy. Và bây giờ hãy nói về việc lấy tài nguyên 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 trống, 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, thì tài nguyên sẽ tiếp tục được được tổ chức bởi chương trình của bạn."

"Điều đó nghe có vẻ công bằng."

"Để giữ nguyên như vậy, 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 các tài nguyên mới nữa.

"Nó giống như các chương trình có thể ngốn hết bộ nhớ..."

"Đại loại như thế. Tin tốt là nếu chương trình của bạn chấm dứt, tất cả tài nguyên sẽ tự động được giải phóng (bản thân hệ điều hành làm việc này)."

"Nếu đó là tin tốt, vậy có nghĩa là có tin xấu?"

"Chính xác là như vậy. Tin xấu là nếu bạn đang viết một ứng dụng máy chủ..."

"Nhưng tôi có viết những lá đơn như vậy không?"

"Rất nhiều ứng dụng máy chủ được viết bằng Java, vì vậy rất có thể bạn sẽ viết chúng cho công việc. Như tôi đã nói, nếu bạn đang viết một ứng dụng máy chủ, thì máy chủ của bạn cần chạy không ngừng trong nhiều ngày, nhiều tuần, nhiều tháng, vân vân."

"Nói cách khác, chương trình không kết thúc và điều đó có nghĩa là bộ nhớ không được giải phóng tự động."

"Chính xác. 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 lâu mới bằng tháng làm việc ổn định! Có thể làm gì đây?"

"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().

"Đây là 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ư sau:

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

"À... Vì vậy, 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), tôi 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."

"Vâng. Tất cả có vẻ đơn giản. Nhưng 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ệ. Phải làm sao đây?"

"Để đả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ẽ 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();
}

"Hmm... Có gì đó không ổn ở đây à?"

"Phải. Mã này sẽ không biên dịch được, 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();
}

"Mọi chuyện bây giờ ổn chứ?"

"Không sao, nhưng nó sẽ không hoạt động nếu xảy ra lỗi khi chúng tôi tạo FileOutputStreamđối tượ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à bây giờ mọi thứ có hoạt động không?"

"Vẫn còn một số 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. Đoạn 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 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."

"Phù... Kết thúc vấn đề là một điều tốt. Tương đối dễ hiểu, nhưng hơi tẻ nhạt, phải không?"

"Đúng vậy. Đó là lý do tại sao những người tạo ra Java đã giúp chúng tôi bằng cách thêm một số cú pháp dễ hiểu. Bây giờ, hãy chuyển sang điểm nổi bật của chương trình, hay đúng hơn là bài học này:

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

"Bắt đầu từ 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."

"Nghe có vẻ hứa hẹn đấy!"

"Trường hợp chung trông khá đơn giản:

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

"Vì vậy, đây là một biến thể khác của try tuyên bố ?"

"Có. Bạn cần thêm dấu ngoặc đơn sau từ trykhóa, sau đó tạo các đố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 sẽ thêm một finallyphần và lệnh 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);
}

"Thật tuyệt! Mã sử ​​dụng try-with-resources ngắn hơn và dễ đọc hơn nhiều. Và chúng tôi 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."

"Tôi rất vui vì bạn thích nó. 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.

Nhiều biến đồng thời

"Bạn có thể thường xuyên gặp phải tình huống khi cần mở nhiều tệp cùng 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à hình thức chung của câu lệnh 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ã ngắn mã dài
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);
}
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();
}

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

"Những gì chúng ta có thể nói là chúng ta nên sử dụng nó."