Java 将一个方法标记为返回一个现有的closeable,以避免在使用站点发出虚假警告
给定一个使用内部可关闭对象的类Java 将一个方法标记为返回一个现有的closeable,以避免在使用站点发出虚假警告,java,eclipse,suppress-warnings,autocloseable,Java,Eclipse,Suppress Warnings,Autocloseable,给定一个使用内部可关闭对象的类MyClass,myCloseable,并提供一个返回该类的方法getCloseable();如果为此类可关闭资源警告配置Eclipse,则每次有人调用getCloseable(),Eclipse都会系统地发出警告,说可关闭资源“可能无法在此位置关闭” 有这样的关于应该关闭的资源的警告是很好的,以防人们忘记关闭它们,所以我希望保持此检查处于启用状态。但在刚才描述的场景中,这是相当令人恼火的。原则上,似乎可以使用注释标记getCloseable()方法,例如,@exi
MyClass
,myCloseable
,并提供一个返回该类的方法getCloseable()
;如果为此类可关闭资源警告配置Eclipse,则每次有人调用getCloseable()
,Eclipse都会系统地发出警告,说可关闭资源“可能无法在此位置关闭”
有这样的关于应该关闭的资源的警告是很好的,以防人们忘记关闭它们,所以我希望保持此检查处于启用状态。但在刚才描述的场景中,这是相当令人恼火的。原则上,似乎可以使用注释标记getCloseable()
方法,例如,@existingCloseable
,告诉编译器“没关系,我只是返回一个已经存在的资源,而不是创建一个新的资源,因此调用方不应该关闭它”
Eclipse或其他IDE是否考虑采用这种注释?我找不到关于它的讨论,所以我怀疑它没有。我想知道为什么:我的例子中描述的模式对我来说似乎很普通和自然。我用注释来考虑的解决方案不会起作用,还是有我没有想到的缺点?
例子
下面是一个(愚蠢的)示例,其中closeable是一个OutputStream
。方法fromPath
会产生一个关于s
未关闭的警告(如果未被抑制),对此我并不介意:这个警告似乎足够了,只需要抑制一次。恼人的部分,也是我问题的对象,是main
方法中出现的警告:“潜在资源泄漏:'目标'可能未关闭”。每当我的类的任何用户使用getTarget
时,都会出现此警告。我想通过注释方法getTarget
一次性禁用它,以便让编译器知道此方法返回调用方不应该关闭的资源。据我所知,这在Eclipse中目前是不受支持的,我想知道为什么
public class MyWriter implements AutoCloseable {
public static void main(String[] args) throws Exception {
try (MyWriter w = MyWriter.fromPath(Path.of("out.txt"))) {
// …
OutputStream target = w.getTarget();
target.flush();
// …
}
}
@SuppressWarnings("resource")
public static MyWriter fromPath(Path target) throws IOException {
OutputStream s = Files.newOutputStream(target);
return new MyWriter(s);
}
private OutputStream target;
public OutputStream getTarget() {
return target;
}
private MyWriter(OutputStream target) {
this.target = target;
}
@Override
public void close() throws Exception {
target.close();
}
}
讨论
我编辑了我的问题,最初问我的代码是否可以修改以避免警告:我意识到这不是我真正感兴趣的问题,我对我所想到的基于注释的解决方案相当感兴趣–对此表示抱歉
我意识到这个例子很愚蠢:没有进一步的上下文,正如一些正确的答案所指出的,看起来我应该包装流的所有方法,并且永远不要将流返回到外部世界。不幸的是,很难让这个例子既现实又保持小规模
然而,有一个例子是众所周知的,因此我不需要在这里给出详细的形式:在servlet中,可以调用,这是一个典型的例子,说明了我的观点:方法getOutputStream()
返回一个调用方不需要关闭的流,但是每次调用Eclipse时(即在每个servlet中),Eclipse都会警告潜在的资源泄漏,这是一种痛苦。同样清楚的是,为什么这个方法的概念没有简单地包装所有内容,而不是返回流本身:获得一个实现这个众所周知的接口的对象是很有用的,因为这样就可以将它与期望与流交互的其他库和方法一起使用
作为我观点的另一个例子,假设getCloseable()
方法仅在内部使用,因此该方法是包可见的,而不是公共的。getCloseable()
的实现可能很复杂,例如继承扮演了一个角色,因此不一定能够像我的小示例中那样用对底层字段的简单访问来替换此调用。在这种情况下,实现一个完整的包装器,而不是返回一个已经存在的接口,就好像根本不接吻一样,只是为了让编译器满意。潜在的资源泄漏问题
这里会出现一个默认禁用的潜在资源泄漏警告,而不是默认启用的常规资源泄漏警告。与资源泄漏警告相反,如果您自己不创建可自动关闭的AutoCloseable
,而是从某个地方获取它,并且不关闭它,无论是通过调用close()
显式地还是通过使用try with Resource隐式地,都会显示潜在的资源泄漏警告
对于简单的情况,是否从某个地方关闭了资源(如在您的案例中),可能会对其进行计算,但并非对所有情况都进行计算。这是同一个问题。例如,可自动关闭的
的创建或关闭可能会委托给再次委托的某个地方,以此类推,如果存在一个if
、一个开关或一个循环,则必须遵循所有分支以确保安全
即使在你简单的例子中,也不是那么简单。通过将唯一一个构造函数设为私有,可以防止类被扩展,否则可以重写close()
,而不调用super.close()
,从而导致从未调用target.close()
。但是由于私有输出流目标代码>不是final
,仍然可能存在真正的资源泄漏,如下所示:
public static void main(String[] args) throws Exception {
try (MyWriter w = MyWriter.toPath(Path.of("out.txt"))) {
// …
OutputStream target = w.getTarget();
w.target = new FileOutputStream("out2.txt");
// …
}
}
因此,如果保存内部AutoCloseable
的字段是final
或privat
并且是有效的final(仅在初始化时设置),并且内部AutoCloseable
将在close()中关闭,则编译器必须检查该类是否不能被重写
外部的可自动关闭的
总而言之,如果您不是自己生成资源而是从某处获取资源,那么就没有gu
public static BufferedOutputStream toBufferedOutputStream(OutputStream outputStream) {
return new BufferedOutputStream(outputStream);
}
public class MyWriter extends OutputStream {
public static void main(String[] args) throws Exception {
try (MyWriter w = MyWriter.of(Path.of("out.txt"))) {
// …
w.flush();
// …
}
}
@SuppressWarnings("resource")
public static MyWriter of(Path target) throws IOException {
OutputStream s = Files.newOutputStream(target);
return new MyWriter(s);
}
private final OutputStream target;
private MyWriter(OutputStream target) {
this.target = target;
}
@Override
public void close() throws IOException {
target.close();
}
@Override
public void write(int b) throws IOException {
target.write(b);
}
@Override
public void flush() throws IOException {
target.flush();
}
}
public class MyWriter {
public static void main(String[] args) throws Exception {
MyWriter.writeToFile(Path.of("out.txt"), w -> {
try {
// …
w.flush();
// …
} catch (IOException e) {
// error handling
}
});
}
public static void writeToFile(Path target, Consumer<OutputStream> writer) throws IOException {
try (OutputStream out = Files.newOutputStream(target);) {
writer.accept(out);
}
}
}