Java 在try-with-resources块中管理多个链接资源的正确习惯用法?

Java 在try-with-resources块中管理多个链接资源的正确习惯用法?,java,try-with-resources,Java,Try With Resources,当只使用一个AutoCloseable资源时,Java 7 try with resources语法(也称为ARM块(自动资源管理))非常好、简短和直接。但是,当我需要声明相互依赖的多个资源时,我不确定什么是正确的习惯用法,例如FileWriter和包装它的BufferedWriter。当然,这个问题涉及到包装某些可自动关闭的资源的任何情况,而不仅仅是这两个特定类 我提出了以下三个备选方案: 1) 我看到的一个简单习惯用法是只声明ARM托管变量中的顶级包装器: static void print

当只使用一个
AutoCloseable
资源时,Java 7 try with resources语法(也称为ARM块(自动资源管理))非常好、简短和直接。但是,当我需要声明相互依赖的多个资源时,我不确定什么是正确的习惯用法,例如
FileWriter
和包装它的
BufferedWriter
。当然,这个问题涉及到包装某些可自动关闭的
资源的任何情况,而不仅仅是这两个特定类

我提出了以下三个备选方案:

1) 我看到的一个简单习惯用法是只声明ARM托管变量中的顶级包装器:

static void printToFile1(String text, File file) {
    try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
        bw.write(text);
    } catch (IOException ex) {
        // handle ex
    }
}
这个又短又漂亮,但它坏了。由于基础
FileWriter
未在变量中声明,因此它永远不会在生成的
finally
块中直接关闭。它将仅通过包装
BufferedWriter
close
方法关闭。问题是,如果从
bw
的构造函数中抛出异常,将不会调用其
close
,因此底层
FileWriter
将不会关闭

2) 在这里,底层和包装资源都在ARM托管变量中声明,因此它们都肯定会被关闭,但是底层
fw.close()
将被调用两次:不仅直接调用,而且还通过包装
bw.close()

这对于这两个都实现了
Closeable
(这是
AutoCloseable
的子类型)的特定类来说应该不是问题,它们的合同规定允许对
close
进行多次调用:

关闭此流并释放与之关联的所有系统资源。如果流已关闭,则调用此方法无效

但是,在一般情况下,我可以使用只实现
自动关闭
(而不是
可关闭
)的资源,这并不保证可以多次调用
关闭

请注意,与java.io.Closeable的close方法不同,此close方法不要求是幂等的。换句话说,多次调用此close方法可能会产生一些明显的副作用,而Closeable.close则需要在多次调用时不起作用。但是,强烈鼓励此接口的实现者使其紧密方法幂等

3) 这个版本在理论上应该是正确的,因为只有
fw
代表需要清理的真实资源。
bw
本身不包含任何资源,它只委托给
fw
,因此只关闭底层
fw

另一方面,语法有点不规则,而且Eclipse发出了一个警告,我认为这是一个假警报,但它仍然是一个必须处理的警告:

资源泄漏:“bw”从未关闭



那么,选择哪种方法呢?还是我遗漏了其他正确的成语?

选项4

如果可以,将资源更改为可关闭,而不是自动关闭。构造函数可以链接这一事实意味着关闭资源两次并非闻所未闻。(在ARM之前也是如此。)下面将详细介绍

选项5

不要非常小心地使用ARM和代码来确保close()不会被调用两次

选项6

不要使用ARM,让finally close()调用自己进行try/catch

为什么我不认为这个问题是ARM独有的

在所有这些示例中,finally close()调用应该位于catch块中。为便于阅读而遗漏

不好,因为fw可以关闭两次。(这对于FileWriter很好,但在您的假设示例中不适用):

不好,因为如果构造BufferedWriter时发生异常,则fw未关闭。(同样,不可能发生,但在您的假设示例中):


由于资源是嵌套的,因此try-with子句也应该是:

try (FileWriter fw=new FileWriter(file)) {
    try (BufferedWriter bw=new BufferedWriter(fw)) {
        bw.write(text);
    } catch (IOException ex) {
        // handle ex
    }
} catch (IOException ex) {
    // handle ex
}

我要说的是,不要用手臂,继续用Closeable。使用类似的方法

public void close(Closeable... closeables) {
    for (Closeable closeable: closeables) {
       try {
           closeable.close();
         } catch (IOException e) {
           // you can't much for this
          }
    }

}

您也应该考虑调用 BuffeDeReDe> <代码>,因为它不只是委派接近文件写入器< /C> >,但它会像“代码>刷新FulsBuffs”进行一些清理。

< P>这是我的备选方案:

1) 对我来说,15年前从传统C++中来到java的最好的事情就是你可以信任你的程序。即使事情变得一团糟,并且经常出错,我希望代码的其余部分都是关于最佳行为和玫瑰香的。实际上,
BufferedWriter
可能会在这里抛出异常。例如,内存不足并不罕见。对于其他decorator,您知道哪个
java.io
包装类会从其构造函数中抛出已检查异常吗?我不。如果依赖于这种模糊的知识,代码的可理解性就没有多大好处

还有“毁灭”。如果存在错误情况,那么您可能不希望将垃圾刷新到需要删除的文件中(未显示该文件的代码)。当然,删除文件也是另一个有趣的错误处理操作

通常,您希望
最终
块尽可能短且可靠。添加刷新无助于实现此目标。对于许多版本,JDK中的一些缓冲类有一个bug,其中
close
中的
flush
异常导致装饰对象上的
close
无法调用。虽然这个问题已经解决了一段时间,但可以从其他实现中得到解决

2) 我们仍然在隐式finally块中刷新(否
FileWriter fw = null;
BufferedWriter bw = null;
try {
  fw = new FileWriter(file);
  bw = new BufferedWriter(fw);
  bw.write(text);
} finally {
  if ( fw != null ) fw.close();
  if ( bw != null ) bw.close();
}
FileWriter fw = null;
BufferedWriter bw = null;
try {
  fw = new FileWriter(file);
  bw = new BufferedWriter(fw);
  bw.write(text);
} finally {
  if ( bw != null ) bw.close();
}
try (FileWriter fw=new FileWriter(file)) {
    try (BufferedWriter bw=new BufferedWriter(fw)) {
        bw.write(text);
    } catch (IOException ex) {
        // handle ex
    }
} catch (IOException ex) {
    // handle ex
}
public void close(Closeable... closeables) {
    for (Closeable closeable: closeables) {
       try {
           closeable.close();
         } catch (IOException e) {
           // you can't much for this
          }
    }

}
try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
    bw.write(text);
}
try (
    FileWriter fw = new FileWriter(file);
    BufferedWriter bw = new BufferedWriter(fw)
) {
    bw.write(text);
}
try (FileWriter fw = new FileWriter(file)) {
    BufferedWriter bw = new BufferedWriter(fw);
    bw.write(text);
}
try (FileWriter fw = new FileWriter(file)) {
    BufferedWriter bw = new BufferedWriter(fw);
    bw.write(text);
    bw.flush();
}
FileWriter fw = null;
BufferedWriter bw = null;
try {
    fw = new FileWriter(file);
    bw = new BufferedWriter(fw);
    bw.write(text);
} finally {
    if (bw != null) bw.close();
    else if (fw != null) fw.close();
}
static AutoCloseable writeFileWriter(FileWriter fw, String txt) throws IOException{
    final BufferedWriter bw  = new BufferedWriter(fw);
    bw.write(txt);
    return new AutoCloseable(){

        @Override
        public void close() throws IOException {
            bw.flush();
        }

    };
}
static void printToFile(String text, File file) {
    try (FileWriter fw = new FileWriter(file)) {
        AutoCloseable w = writeFileWriter(fw, text);
        w.close();
    } catch (Exception ex) {
        // handle ex
    }
}
static void printToFile(String text, File file) {
    try (FileWriter fw = new FileWriter(file);
        AutoCloseable w = writeFileWriter(fw, text)){

    } catch (Exception ex) {
        // handle ex
    }
}
static BufferedWriter createBufferedWriterFromFile(File file)
  throws IOException {
  // If constructor throws an exception, no resource acquired, so no release required.
  FileWriter fileWriter = new FileWriter(file);
  try {
    return new BufferedWriter(fileWriter);  
  } catch (IOException newBufferedWriterException) {
    try {
      fileWriter.close();
    } catch (IOException closeException) {
      // Exceptions in cleanup code are secondary to exceptions in primary code (body of try),
      // as in try-with-resources.
      newBufferedWriterException.addSuppressed(closeException);
    }
    throw newBufferedWriterException;
  }
}
try (BufferedWriter writer = createBufferedWriterFromFile(file)) {
  // Work with writer.
}
static <T extends AutoCloseable, U extends AutoCloseable, V>
    T createChainedResource(V v) throws Exception {
  // If constructor throws an exception, no resource acquired, so no release required.
  U u = new U(v);
  try {
    return new T(u);  
  } catch (Exception newTException) {
    try {
      u.close();
    } catch (Exception closeException) {
      // Exceptions in cleanup code are secondary to exceptions in primary code (body of try),
      // as in try-with-resources.
      newTException.addSuppressed(closeException);
    }
    throw newTException;
  }
}