java try-with-resource不使用scala

时间:2016-10-05 05:25:24

标签: java scala try-with-resources

在Scala应用程序中,我尝试使用java nio try-with-resource构造从文件中读取行。

Scala版本2.11.8
Java版本1.8

try(Stream<String> stream = Files.lines(Paths.get("somefile.txt"))){
    stream.forEach(System.out::println); // will do business process here
}catch (IOException e) {
    e.printStackTrace(); // will handle failure case here
}  

但是编译器抛出了像
的错误 ◾notfound:value stream
◾尝试没有捕获或最终相当于将其身体放在一个块中;没有例外处理。

不确定是什么问题。我是使用Java NIO的新手,所以非常感谢任何帮助。

3 个答案:

答案 0 :(得分:21)

在scala中没有直接支持javas try-with-resources构造,但是通过应用贷款模式,您可以很容易地构建自己的支持。以下是一个简单但不是最佳的例子,很容易理解。这个答案后面会给出一个更正确的解决方案:

import java.lang.AutoCloseable

def autoClose[A <: AutoCloseable,B](
        closeable: A)(fun: (A) ⇒ B): B = {
    try {
        fun(closeable)
    } finally {
        closeable.close()
    }
}

这定义了一个可重用的方法,它的工作方式与java中的try-with-resource构造非常相似。它的工作原理是两个参数。第一个是Autoclosable实例的子类,第二个是一个函数,它采用相同的Autoclosable类型作为paremeter。函数参数的返回类型用作方法的返回类型。然后,该方法在try中执行该函数,并在其finally块中关闭autocloseble。

您可以像这样使用它(此处用于在流上获取findAny()的结果。

val result: Optional[String] = autoClose(Files.lines(Paths.get("somefile.txt"))) { stream ⇒
    stream.findAny()
}

如果你想捕捉异常,你有两个选择。

  1. 在stream.findAny()调用周围添加一个try / catch块。

  2. 或者在autoClose方法中向try块添加一个catch块。请注意,只有在catch块中的逻辑可以从调用autoClose的所有位置使用时,才应该这样做。

  3. 请注意,正如Vitalii Vitrenko指出的那样,如果客户端提供的函数和AutoCloseable上的close方法都抛出异常,则此方法将吞噬close方法的异常。 Javas try-with-resources处理这个问题,我们可以通过使它更复杂来使autoClose这样做:

      def autoClose[A <: AutoCloseable,B](
          closeable: A)(fun: (A) ⇒ B): B = {
    
        var t: Throwable = null
        try {
          fun(closeable)
        } catch {
          case funT: Throwable ⇒
            t = funT
            throw t
        } finally {
          if (t != null) {
            try {
              closeable.close()
            } catch {
              case closeT: Throwable ⇒
                t.addSuppressed(closeT)
                throw t
            }
          } else {
            closeable.close()
          }
        }
      }
    

    这可以通过存储客户端函数抛出的潜在异常,并将close方法的潜在异常作为被抑制的异常添加到它。这非常接近oracle如何解释try-with-resource实际上是这样做的:http://www.oracle.com/technetwork/articles/java/trywithresources-401775.html

    然而,这是Scala,很多人更愿意以更实用的方式编程。在更实用的方式中,该方法应返回Try,而不是抛出异常。这避免了抛出异常的副作用,并使客户明白响应可能是应该处理的失败(正如Stas的回答中所指出的)。在功能实现中,我们还希望避免使用var,因此天真的尝试可能是:

      // Warning this implementation is not 100% safe, see below
      def autoCloseTry[A <: AutoCloseable,B](
          closeable: A)(fun: (A) ⇒ B): Try[B] = {
    
        Try(fun(closeable)).transform(
          result ⇒ {
            closeable.close()
            Success(result)
          },
          funT ⇒ {
            Try(closeable.close()).transform(
              _ ⇒ Failure(funT),
              closeT ⇒ {
                funT.addSuppressed(closeT)
                Failure(funT)
              }
            )
          }
        )
      }
    

    这可以像这样调用:

        val myTry = autoCloseTry(closeable) { resource ⇒
          //doSomethingWithTheResource
          33
        }
        myTry match {
          case Success(result) ⇒ doSomethingWithTheResult(result)
          case Failure(t) ⇒ handleMyExceptions(t)
        }
    

    或者你可以在myTry上调用.get使其返回结果,或抛出异常。

    然而,正如Kolmar在评论中指出的那样,由于返回语句在scala中的工作方式,这种实现存在缺陷。请考虑以下事项:

      class MyClass extends AutoCloseable {
        override def close(): Unit = println("Closing!")
      }
    
      def foo: Try[Int] = {
         autoCloseTry(new MyClass) { _ => return Success(0) }
      }
    
      println(foo)
    

    我们希望这会打印Closing!,但它不会。这里的问题是函数体内的显式return语句。它使该方法跳过autoCloseTry方法中的逻辑,从而只返回Success(0),而不关闭资源。

    为了解决这个问题,我们可以创建两个解决方案的混合,一个具有返回Try的功能API,但使用基于try / finally块的经典实现:

        def autoCloseTry[A <: AutoCloseable,B](
            closeable: A)(fun: (A) ⇒ B): Try[B] = {
    
          var t: Throwable = null
          try {
            Success(fun(closeable))
          } catch {
            case funT: Throwable ⇒
              t = funT
              Failure(t)
          } finally {
            if (t != null) {
              try {
                closeable.close()
              } catch {
                case closeT: Throwable ⇒
                  t.addSuppressed(closeT)
                  Failure(t)
              }
            } else {
              closeable.close()
            }
          }
        }
    

    这应该可以解决问题,并且可以像第一次尝试一样使用。然而,它表明这有点容易出错,并且错误的实现已经在这个答案中作为推荐版本已经有一段时间了。因此,除非您尝试避免使用多个库,否则应正确考虑使用库中的此功能。我认为已经有另一个答案指向一个,但我的猜测是有多个库,以不同的方式解决了这个问题。

答案 1 :(得分:1)

你已经在其中一个答案方法中提到过:

  def autoClose[A <: AutoCloseable, B](resource: A)(code: A ⇒ B): B = {
    try
      code(resource)
    finally
      resource.close()
  }

但我认为以下内容更为优雅:

  def autoClose[A <: AutoCloseable, B](resource: A)(code: A ⇒ B): Try[B] = {
    val tryResult = Try {code(resource)}
    resource.close()
    tryResult
  }

使用最后一个恕我直言,它更容易处理控制流程。

答案 2 :(得分:1)

或者,您可以使用Choppy的TryClose monad以类似于Scala的Try的组合方式进行for-comprehension。

val ds = new JdbcDataSource()
val output = for {
  conn  <- TryClose(ds.getConnection())
  ps    <- TryClose(conn.prepareStatement("select * from MyTable"))
  rs    <- TryClose.wrap(ps.executeQuery())
} yield wrap(extractResult(rs))

以下是使用您的信息流进行操作的方法:

val output = for {
  stream  <- TryClose(Files.lines(Paths.get("somefile.txt")))
} yield wrap(stream.findAny())

更多信息: https://github.com/choppythelumberjack/tryclose