【问题标题】:Shortest way to iterate over non-list?迭代非列表的最短方法?
【发布时间】:2016-08-29 15:28:43
【问题描述】:

假设我有 3 个要关闭的 Scanner 实例。

我可以的

sc.close()

对于每个扫描器。

或者我可以做类似的事情

for (Scanner sc: new Scanner[]{sc1,sc2,sc3}) {
    sc.close();
}

在 Java 8 中是否有更短的方法?

类似的东西?

{sc1,sc2,sc3}.forEach((sc) -> sc.close());

【问题讨论】:

标签: java java-8


【解决方案1】:

从 Java 7 开始,您应该使用 try-with-resources

try(Scanner sc1 = new Scanner(""); 
    Scanner sc2 = new Scanner(""); 
    Scanner sc3 = new Scanner("")){

}
//all scanners get closed implicitly

所以您根本不需要任何代码。

所有 for-each 或流构造的问题是,理论上 - 如果第一个 close() 在调用底层源的 close() 方法时出现异常,则以下扫描程序将不会关闭。 Scanner.close() 实现会捕获任何 IOException,但不会捕获其他可能发生的异常。

try-with-resources 结构可以解决这个问题,而循环则不会。


编辑:虽然您的问题针对的是更通用的方法,但上述解决方案是对您的特定问题的回应:处理 AutoCloseable 资源,无论如何都应该与try-with-resources 构造,根本不需要对 close 方法进行特殊处理(= 您特定问题的最短解决方案)。

关于处理任意项目(不是资源)的更一般的问题,Java 至少有两种选择:

从 Array/Varargs 创建一个列表并对其进行迭代

for(YourItemType item : Arrays.asList(your,items,here)) {
  //do something
}

从 Array/Varargs 创建一个 Stream 并对其应用函数

Stream.of(your,items,here).forEach(item -> { doSomething});

当然“doSomething”可以替换为方法引用

Stream.of(your,items,here).forEach(this::myMethod);
...
void myMethod(YourItemType item){
  //doSomething
} 

这种方法的问题是,检查的异常必须在 lambda 表达式中显式处理。让我们以上面的例子,让myMethod抛出一个检查异常

void myMethod(YourItemType item) throws Exception

在这种情况下,您的流语句必须看起来像

Stream.of(your,items,here).forEach(item -> {
  try {
    myMethod(item);
  } catch (Exception e){
    //omit or throw new RuntimeException(e);
  };

这看起来不太好。但是我们可以将 lambda 主体放在一个单独的方法中

void myMethodQuietly(YourItemType item) {
  try {
    myMethod(item);
  }catch(Exception e){
    //omit or throw new RuntimeException(e);
  }
}

Stream.of(your,items,here).forEach(this::myMethodQuietly);

这种方法可能对您的特定资源问题感兴趣。我们可以将所有这些放入一个 CompositeAutoCloseable 中,该 CompositeAutoCloseable 获取在类之外创建的资源,所有这些资源都应该在调用 close() 时安全关闭

public class CompositeAutoCloseable implements AutoCloseable {

  private List<Closeable> resources;

  public CompositeAutoCloseable(Closeable... resources) {
    this.resources = Arrays.asList(resources);
    //you could use a stream here too
  }

  @Override
  public void close() {
      this.resources.stream().forEach(this::closeQuietly);
  }

  void closeQuietly(Closeable res) {
    if(res == null)  {
        return;
    }
    try {
        res.close();
    }catch(Exception e){
        //omit
    }
  } 
}

一旦你有了这样一个帮助类,你就可以通过 try-with-resources 再次使用它。

try(CompositeAutoCloseable cac = new CompositeAutoCloseable(sc1,sc2,sc3)) {
  //do something
}

与初始解决方案相比,我让您决定这是否有意义;)

【讨论】:

  • 这个答案对我来说似乎最有意义。利用AutoCloseables 而不是硬流来显式关闭Scanners 似乎是最自然的解决方案。
  • 请注意,try-with-resource 仍然不是 100% 安全的。下面是一个来自 C# 的简单示例,说明相应构造可能存在问题:stackoverflow.com/a/21123756/1348195
  • @Benjamin Gruenbaum:那篇文章使用了很多 Java 中不存在的 C# 结构。通常,代码使用 Scanner 不负责修复类Scanner 的构造函数中的潜在错误。当然,它不是“100% 安全”,但没有什么是。您如何防止有人拔插头?
  • 这并不能真正回答问题。当然,您想尽可能使用try,但这并不总是一种选择。例如,当您的类在其构造函数中创建三个 Scanner 实例,并在其自己的 close() 方法中关闭它们。
  • @BlueRaja - Danny Pflughoeft:即便如此,您最好还是使用try 构造来确保所有三个都安全关闭:try(Scanner toClose1=sc1; Scanner toClose2=sc2) { sc3.close(); }
【解决方案2】:

虽然有时代码的长度与其质量之间存在相关性,但这并不是选择代码的好标准。

我可能会使用可变参数并执行以下操作:

private void closeScanner(Scanner... scanners) {
    // Faff around with Java 8 in here if you like.
    for (Scanner s : scanners) {
        s.close();
    }
}

    // Some code.
    closeScanner(s, t, u);

【讨论】:

  • 异常安全失败
  • @basilevs - Scanner.close 不会抛出异常。
  • @OldCurmudgeon:好吧,它只抛出未经检查的异常。
  • @SteveJessop - 那么他们会在其他地方被抓到,不是吗?仍然不清楚这是一个异常安全失败的原因。
  • 如果对close() 的第一次调用确实抛出了异常,那么无论它是否被其他地方捕获,第二次和第三次调用都不会被尝试。如果您希望在失败后尝试进行资源清理,但没有尝试,这就是 Basilevs 所说的“异常安全失败”的含义,这就是 try-with-resources 旨在帮助做的事情。
【解决方案3】:

如果实例化与处置分开,请使用 Guava 的com.google.common.io.Closer

【讨论】:

    猜你喜欢
    • 2011-11-18
    • 2013-10-01
    • 2022-06-13
    • 1970-01-01
    • 1970-01-01
    • 2022-10-19
    • 2019-04-08
    • 2015-03-07
    • 2012-05-05
    相关资源
    最近更新 更多