java try-with-resource 不使用 scala

java try-with-resource not working with scala

在 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
}  

但是编译器会抛出类似
的错误 ◾未找到:价值流
◾没有捕获或最终的尝试等同于将其 body 放入块中;不处理任何异常。

不确定是什么问题。我是使用 Java NIO 的新手,因此非常感谢您的帮助。

如果您使用的是 Scala 2.13,那么您应该使用 Using object:

import scala.util.Using
val a: Try[Int] = Using(new FileInputStream("/tmp/someFile")) { fileInputStream =>
  // Do what you need in fith you fileInputStream here.
}

它有两个功能。第一个是可以创建或提供可关闭资源的函数,第二个函数是将可关闭资源作为参数并可以将其用于某些事情的函数。然后使用 will 简单地为您执行以下操作:

  1. 调用第一个函数来创建可关闭资源。
  2. 调用第二个函数,并提供资源作为参数。
  3. 保留第二个函数的 returned 值。
  4. 关闭资源。
  5. Return 它从包含在 Try 中的第二个函数获得的值(或异常)。

Using 可以用于实现 AutoCloseable 的 类 之外的许多其他用途,您只需提供一个隐式值,告诉 Using 如何关闭您的特定资源。

在旧版本的 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 类型的函数。 return 类型的函数参数,被用作 return 类型的方法。然后该方法在 try 中执行该函数,并在其 finally 块中关闭自动关闭。

你可以这样使用它(这里用来获取流上findAny()的结果。

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

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

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

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

请注意,正如 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()
      }
    }
  }

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

然而这是 Scala,很多人更喜欢以更函数式的方式编程。以更实用的方式,该方法应该 return 一个 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 使其成为 return 结果,或者抛出异常。

然而,正如 Kolmar 在评论中指出的那样,由于 return 语句在 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 方法中的逻辑,因此只是 returns Success(0),而不关闭资源。

要解决该问题,我们可以混合使用这两种解决方案,其中一种具有 return 尝试的功能 API,但使用基于 [=75] 的经典实现=]块:

    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()
        }
      }
    }

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

您有一个答案中已经提到的方法:

  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
  }

有了最后一个恕我直言,处理控制流就更容易了。

或者,您可以使用 Choppy 的(免责声明:我是作者)TryClose monad 以类似于 Scala 的 Try 的可组合方式进行理解。

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