1. 外部资源
当 Java 程序运行时,有时它会与 Java 机器外部的实体进行交互。例如,磁盘上的文件。这些实体通常称为外部资源。内部资源是在 Java 机器内部创建的对象。
通常,交互遵循以下方案:
追踪资源
操作系统严格跟踪可用资源,并控制不同程序对它们的共享访问。例如,如果一个程序更改了一个文件,那么另一个程序就不能更改(或删除)该文件。这个原则并不局限于文件,但它们提供了最容易理解的例子。
操作系统具有允许程序获取和/或释放资源的函数 (API)。如果资源繁忙,则只有获取它的程序才能使用它。如果资源是免费的,那么任何程序都可以获取它。
想象一下,您的办公室共用咖啡杯。如果有人拿了一个杯子,那么其他人就不能再拿了。但是一旦杯子被使用、清洗并放回原位,任何人都可以再次使用它。公共汽车或地铁上的座位情况是一样的。如果有空位,那么任何人都可以坐。如果一个座位被占用,则它由占用它的人控制。
获取外部资源。
每次您的 Java 程序开始使用磁盘上的文件时,Java 机器都会请求操作系统对其进行独占访问。如果资源空闲,则 Java 机器会获取它。
但是当你使用完这个文件之后,这个资源(文件)必须被释放,也就是说你需要通知操作系统你不再需要它了。如果您不这样做,那么该资源将继续由您的程序持有。
操作系统维护着每个正在运行的程序占用的资源列表。如果你的程序超过了分配的资源限制,那么操作系统将不再给你新的资源。
好消息是,如果您的程序终止,所有资源都会自动释放(操作系统本身会这样做)。
坏消息是,如果您正在编写服务器应用程序(并且很多服务器应用程序是用 Java 编写的),您的服务器需要能够连续运行数天、数周和数月而不停止。如果您每天打开 100 个文件并且不关闭它们,那么几周后您的应用程序将达到其资源限制并崩溃。这远远低于数月的稳定工作。
2.close()
方法
使用外部资源的类有一种释放它们的特殊方法:close()
.
下面我们提供了一个程序示例,该程序将某些内容写入文件,然后在完成后关闭文件,即释放操作系统的资源。它看起来像这样:
代码 | 笔记 |
---|---|
|
文件的路径。 获取文件对象:获取资源。 写入文件 关闭文件 - 释放资源 |
使用文件(或其他外部资源)后,您必须close()
在链接到外部资源的对象上调用该方法。
例外情况
一切看起来都很简单。但是程序运行时可能会出现异常,外部资源不会被释放。那是非常糟糕的。
为了确保该close()
方法始终被调用,我们需要将我们的代码包装在一个try
--块中,并将该方法添加到该块catch
中。它看起来像这样:finally
close()
finally
try
{
FileOutputStream output = new FileOutputStream(path);
output.write(1);
output.close();
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
output.close();
}
这段代码不会编译,因为output
变量是在try {}
块内声明的,因此在finally
块中是不可见的。
让我们修复它:
FileOutputStream output = new FileOutputStream(path);
try
{
output.write(1);
output.close();
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
output.close();
}
没关系,但是如果我们在创建对象时发生错误,它就不会起作用FileOutputStream
,而且很容易发生这种情况。
让我们修复它:
FileOutputStream output = null;
try
{
output = new FileOutputStream(path);
output.write(1);
output.close();
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
output.close();
}
还有一些批评。首先,如果创建对象时发生错误FileOutputStream
,则output
变量将为空。必须在块中考虑这种可能性finally
。
其次,该close()
方法总是在finally
块中调用,这意味着它在try
块中是没有必要的。最终代码将如下所示:
FileOutputStream output = null;
try
{
output = new FileOutputStream(path);
output.write(1);
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
if (output != null)
output.close();
}
即使我们不考虑catch
块,可以省略,那么我们的3行代码就变成了10行。但是我们基本上只是打开文件写了1。有点麻烦,你不觉得吗?
3.-try
资源
在这里,Java 的创造者决定给我们撒上一些语法糖。从第 7 版开始,Java 有了新的try
-with-resources 语句。
它的创建正是为了解决强制调用方法的问题close()
。一般情况看起来很简单:
try (ClassName name = new ClassName())
{
Code that works with the name variable
}
这是try
声明的另一种变体。需要在try
关键字后面加上括号,然后在括号内创建带有外部资源的对象。对于括号中的每个对象,编译器都会添加一个finally
部分和对方法的调用close()
。
下面是两个等价的例子:
长码 | 使用 try-with-resources 编写代码 |
---|---|
|
|
使用 -with-resources 的代码try
更短且更易于阅读。我们拥有的代码越少,出现拼写错误或其他错误的可能性就越小。
顺便说一下,我们可以在 -with-resources 语句中添加catch
和finally
块try
。或者,如果不需要,您可以不添加它们。
4.同时有几个变量
顺便说一句,您可能经常会遇到需要同时打开多个文件的情况。假设您正在复制一个文件,那么您需要两个对象:要从中复制数据的文件和要将数据复制到的文件。
在这种情况下,try
-with-resources 语句允许您在其中创建一个但多个对象。创建对象的代码必须用分号分隔。以下是此语句的一般外观:
try (ClassName name = new ClassName(); ClassName2 name2 = new ClassName2())
{
Code that works with the name and name2 variables
}
复制文件示例:
长码 | 短代码 |
---|---|
|
|
那么,我们能在这里说什么呢?try
-with-resources 是一件很棒的事情!
5.AutoCloseable
界面
但这还不是全部。细心的读者会立即开始寻找限制该陈述应用方式的陷阱。
try
但是,如果类没有方法,-with-resources 语句如何工作close()
?好吧,假设什么都不会被调用。没有方法,没有问题。
try
但是如果类有多个close()
方法,-with-resources 语句如何工作呢?他们需要将参数传递给他们吗?而且这个类没有没有close()
参数的方法?
我希望你真的问过自己这些问题,也许还有其他问题。
为了避免此类问题,Java 的创建者提出了一个名为 的特殊接口AutoCloseable
,它只有一个方法 — close()
,没有参数。
他们还添加了限制,即只有实现类的对象AutoCloseable
才能在try
-with-resources 语句中声明为资源。因此,此类对象将始终具有close()
不带参数的方法。
顺便说一下,您认为try
-with-resources 语句可以将一个对象声明为资源,该对象的类有自己的close()
不带参数的方法但没有实现AutoCloseable
?
坏消息:正确答案是否定的——类必须实现接口AutoCloseable
。
好消息: Java 有很多实现此接口的类,因此很可能一切都会按预期工作。
GO TO FULL VERSION