如果您使用的是 Scala 2.13,那么您应该使用 Using 对象:
import scala.util.Using
val a: Try[Int] = Using(new FileInputStream("/tmp/someFile")) { fileInputStream =>
// Do what you need in fith you fileInputStream here.
}
它需要两个函数。第一个函数是可以创建或提供可闭合资源的函数,第二个函数是将可闭合资源作为参数,并可以将其用于某些事情的函数。然后,使用 will 简单来说,为您执行以下操作:
- 调用第一个函数以创建可关闭的资源。
- 调用第二个函数,并将资源作为参数提供。
- 保留第二个函数的返回值。
- 对资源调用 close。
- 返回从包装在 Try 中的第二个函数获得的值(或异常)。
除了实现 AutoCloseable 的类之外,Use 可以用于许多其他事情,您只需要提供一个隐式值,告诉 Using 如何关闭您的特定资源。
在旧版本的scala中,没有直接支持javas try-with-resources construct,但是你可以通过应用贷款模式来轻松构建自己的支持。下面是一个简单但不是最佳的示例,很容易理解。这个答案后面给出了一个更正确的解决方案:
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 实例的 a 子类,其次它采用一个函数,该函数采用与 Paremeter 相同的 Autoclosable 类型。函数参数的返回类型用作方法的返回类型。然后,该方法在 try 中执行该函数,并在其 finally 块中关闭自动关闭。
你可以像这样使用它(这里用来获取流上findAny()的结果。
val result: Optional[String] = autoClose(Files.lines(Paths.get("somefile.txt"))) { stream ⇒
stream.findAny()
}
如果您想执行捕获异常,则有 2 个选择。
在 stream.findAny() 调用周围添加一个 try/catch 块。
或者将捕获块添加到 autoClose 方法中的 try 块。请注意,仅当 catch 块内的逻辑可在调用 autoClose 的所有位置使用时,才应执行此操作。
请注意,正如Vitali vitrenko所指出的那样,如果客户端提供的函数和AutoCloseable上的关闭方法都抛出异常,则此方法将吞噬关闭方法中的异常。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解释尝试资源实际上是在做这件事的方式非常接近: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在评论中指出的那样,由于return语句在scala中的工作方式,这种实现是有缺陷的。请考虑以下事项:
class MyClass extends AutoCloseable {
override def close(): Unit = println("Closing!")
}
def foo: Try[Int] = {
autoCloseTry(new MyClass) { _ => return Success(0) }
}
println(foo)
我们希望它能打印出 Close!,但它不会。这里的问题是函数体内的显式 return 语句。它使该方法跳过 autoCloseTry 方法中的逻辑,从而只返回 Success(0),而不关闭资源。
为了解决这个问题,我们可以创建2个解决方案的组合,其中一个具有返回Try的功能API,但使用基于try/final块的经典实现:
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()
}
}
}
这应该可以解决问题,并且可以像第一次尝试一样使用。但是,它表明这有点容易出错,并且错误的实现已经在此答案中作为推荐版本存在了相当长的一段时间。因此,除非您试图避免使用许多库,否则您应该正确考虑从库中使用此功能。我认为已经有一个答案指向一个,但我的猜测是有乘法库,以不同的方式解决这个问题。