实际问题
这……很复杂。一般规则是,创建资源的人也必须安全地关闭它,并且由于您的代码创建了一个 Scanner,IDE 会告诉您:嘿,您应该关闭它。
问题是,在此处关闭该扫描仪是错误的:扫描仪环绕 System.in,这不是您创建的资源,但 scanner.close() 会关闭底层流(System.in 本身),而您不希望这样:这不是您的责任,实际上会主动损害事物;现在你再也不能从 sysin 读取数据了。
问题是,IDE 不能真正知道这一点。潜在的问题是System.in 在很多方面都是设计得非常糟糕的 API,但是 [A] 它已经 30 岁了,当时很难知道这一点;事后看来,我们现在知道了,[B] oracle 还没有开始制作 sysin/out/err API 的第二个版本,而且它不在议程上。
这会给 IDE 带来麻烦:设置一些使用资源的模式和规则相对容易,这样您就不会遇到“您创建的过滤器围绕您未创建的资源”的任何问题,但您不能在不编写任何框架的情况下将它们与 sysin/err/out 一起使用,这对新手来说有点过分。告诉那些在 Java 编码中迈出第一步的人先去下载一些第三方库来稍微清理一下 sysin/out/err 交互,这也不是一个好主意。
因此,我们陷入了困境。 IDE 不应该对此发出警告,但他们很难检测到这是一个奇特的场景,您拥有自己制作的资源,但您不需要关闭,实际上不应该关闭。
您可以关闭“未关闭资源”的设置,这无疑是视频教程所做的,但这是一个有用的警告。只是......被这个不再有意义的愚蠢的旧 API 所阻碍。
一些深入的解释
有资源。这些都是实现AutoClosable的东西,而且非常多。让我们关注那些代表 I/O 的东西:层次结构中的顶级类型是 Writer、Reader、InputStream 和 OutputStream(我们将它们统称为 WRIO)。它们都是 AutoCloseable,大多数 IDE(不正确?)都抱怨这些资源未关闭。但是,这过于简单化了。
您可以将所有 WRIO 的世界拆分为:
- 实际资源(它们直接代表基于操作系统的底层概念,如果您不关闭它们,则会导致某些资源匮乏,也就是“泄漏”)。
new FileInputStream、socket.getInputStream - 等等,这些代表实际资源
- 虚拟资源 - 它们的作用类似于资源,但实际上并不代表垃圾收集器尚未修复的可以饿死的资源。
new ByteArrayInputStream,将 StringBuilders 变成 Readers 等等。
- 过滤器 - 这些过滤器环绕资源并在“传输中”对其进行修改。此类过滤器本身不会捕获任何可饥饿的资源。如果你
close() 他们,他们也会在他们包装的东西上调用 close 。扫描仪是一个过滤器。
关闭它们的规则归结为:
- 实际资源 - 必须由制作它们的人安全关闭。如果您不这样做,IDE 警告是有保证的。请注意,您没有创建
System.in,因此不适用于那里。
- 虚拟资源 - 您可以关闭它们,但不是必须的。如果 IDE 对它们发出警告,请在其周围使用 try-with-resources,虽然很烦人,但也不太难解决。
- 过滤器 - 棘手。
过滤器的问题
如果它是提供给您的过滤器,您的意图是关闭它:
BufferedReader br = Files.newBufferedReader(somePath);
那么无法关闭br就是资源泄漏; IDE 警告是有保证的。
如果它是您制作的过滤器,则包裹您也制作的 WRIO:
InputStream raw = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(raw, StandardCharsets.UTF_8));
(这是1个真实资源,被一个过滤器WRIO(InputStreamReader)包裹,然后那个过滤器被另一个过滤器WRIO包裹):那么资源泄漏就是raw,如果你没有安全关闭br,这不是资源泄漏。这可能是一个错误(如果您关闭 raw 而不先关闭/刷新 br,则缓冲区中的一堆字节不会被写出),但不是资源泄漏。关于未能关闭 br 的 IDE 警告是错误的,但不会太有害,因为您可以在它周围使用 try-with-resources,这顺便也保证了“由于未能清除缓冲过滤器 WRIO 而导致的错误' 不能再发生了。
那么,问题就来了:
制作一个环绕资源的过滤器WRIO您没有制作并且没有责任关闭:您不应该主动关闭这些过滤器WRIO,因为这最终会关闭底层资源,而你不想要那个。
这里的 IDE 警告非常糟糕且令人讨厌,但 IDE 很难意识到这一点。
设计方案
通常,您可以通过从不进入该场景来解决此问题。例如,System.in 应该有更好的 API;这个 API 看起来像:
try (Scanner s = System.newStandardIn()) {
// use scanner here
}
并具有关闭 s 不关闭 System.in 本身的属性(它几乎什么都不做;设置一个布尔标志以在完成任何进一步的读取调用时抛出异常,或者甚至可能做字面上什么都没有)。现在 IDE 警告充其量是过分热心的,但是听从它的建议并安全地关闭您的扫描仪现在不再主动在您的代码中引入错误。
不幸的是,这个不错的 API 还不存在(还没有?)。因此,我们陷入了这种恼人的场景,一个有用的 IDE 警告系统会因为糟糕的 API 设计而主动误导您。如果你真的想,你可以写它:
public static Scanner newStandardIn() {
Scanner s = new Scanner(System.in) {
@Override public void close() {}
};
// hey, while we're here, lets fix
// another annoying wart!
s.useDelimiter("\r?\n");
return s;
}
现在您可以按照它的建议留意这些警告:
public static void main(String[] args) {
String name;
int age;
try (Scanner s = newStandardIn()) {
System.out.print("What is your name: ");
// use next() to read entire lines -
// that useDelimiter fix made this possible
name = s.next();
System.out.print("What is your age: ");
age = s.nextInt();
}
// use name and age here
}
没有 IDE 警告,也没有错误。