Using a variable in finally block

2020-04-16 04:08发布

问题:

Here is code in Scala:

def write() = {
    try {
      val out = new PrintWriter(new BufferedWriter(new FileWriter(fileName, true)))
      out.println("123")
      out.close
    } catch {
      case e: IOException => {}
    }

   //finally {
     //out.close // ops, it's not visible in this context
   //}
  }

It would be better to have "out.close" in finally block, isn't it? But I don't want to use var.

My question is, how do I achieve that?

回答1:

A variable defined in a block is local to that block. So if you insist on using try/finally manually you will have to move the val out of the block.

However, what you are trying to achieve is to create a resource, use it in a block, and call a close method on it when leaving the block, no matter whether you leave the block normally or abnormally via an exception. This is an extremely common problem, so there is already a library for it, called Scala ARM. ARM stands for automatic resource management.

Here is the basic usage:

import resource._
for(input <- managed(new FileInputStream("test.txt")) {
  // Code that uses the input as a FileInputStream
}

There was some talk of moving this construct to the scala standard library, so in the future you probably won't even need an external dependency.

I would recommend using a library for something like this. It is just one more line in your build.sbt. But for educational purposes, here is how you would roll your own:

def managed[T <: AutoCloseable](resource:T) = new Traversable[T] {
  def foreach[U](f:T=>U) {
    try {
      f(resource)
    } finally {
      resource.close()
    }
  }
}

And here is how to use it

scala> for(reader<-managed(new java.io.FileReader("/etc/passwd"))) { println(reader.read()) }
114

scala> for(reader<-managed(new java.io.FileReader("/etc/shadow"))) { println(reader.read()) }
java.io.FileNotFoundException: /etc/shadow (Permission denied)
...

You will still get the exception, but close will be called. Of course if close throws an exception as well you this will hide the original exception. Little details like this are probably handled better in scala ARM.



回答2:

This is what I use to manage closable resources passed to function returning and not retuning futures

  def withClosable[ T, C <: Closeable ]( closable: C )( f: C ⇒ T ) = try { f( closable ) } finally { IOUtils closeQuietly closable }

  def withFutureClosable[ T <: Future[Any], C <: Closeable ]( closable: C )( f: C ⇒ T ) = f( closable ) andThen {

        case _ => IOUtils closeQuietly closable
    } 
}

I use IOUtils from commons-io to simplify the call to actually close the resource. A simple try { closable.close() } catch { case _ => /* blah */ } would do

Example usage:

withClosable(new FileInpustream("f")) { stream => /* read the stream */ }



回答3:

The loan pattern is more usual for this use case, but since anything goes on Stack Overflow, you can construct the expression you're looking for with Try.

Try deserves more exposure as a handy tool.

scala> import util._
import util._

scala> import io._
import io._

Try to open the file --

scala> def f =
     | Try (Source.fromFile("foo.text")) map { in =>

then do something with it, packaging the result in a tuple with the i/o source -- note that when you do a value definition in a for-comprehension, this is what it does --

     |   (in, Try(in.getLines.mkString("/")))
     | } flatMap {

then close the source and yield the result of the computation --

     |   case (in, res) =>
     |     in.close()
     |     res
     | }
f: scala.util.Try[String]

Uncommented:

scala> def f =
     | Try (Source.fromFile("foo.text")) map { in =>
     |   (in, Try(in.getLines.mkString("/")))
     | } flatMap {
     |   case (in, res) =>
     |     in.close()
     |     res
     | }
f: scala.util.Try[String]

scala> f
res1: scala.util.Try[String] = Failure(java.io.FileNotFoundException: foo.text (No such file or directory))

Create the test file with some classic humour text, then try again:

scala> f
res2: scala.util.Try[String] = Success(Now is the time/for all good dogs/to lie.)

You can sugarcoat it as a for-comprehension, though observe the extra flatten, since you get a map instead of flatMap from the yield:

scala> def g = (for {
     |   in <- Try (Source.fromFile("foo.text"))
     |   res = Try(in.getLines.mkString("/"))
     | } yield {
     |   in.close()
     |   res
     | }).flatten
g: scala.util.Try[String]

scala> g
res2: scala.util.Try[String] = Success(Now is the time/for all good dogs/to lie.)

What if we want to fail if the close fails?

I don't want to type in all that stuff into the REPL again!

scala> :hi             // :history
[snip]
2490  def g = (for {
2491    in <- Try (Source.fromFile("foo.text"))
2492    res = Try(in.getLines.mkString("/"))
2493  } yield {
2494    in.close()
2495    res
2496  }).flatten
2497  :hi

scala> :edit 2490+7    // or just :edit 2490-
+import util._
+import io._
+def g = (for {
+  in <- Try (Source.fromFile("foo.text"))
+  res = Try(in.getLines.mkString("/"))
+} yield {
+  val ok = Try(in.close())
+  res transform (s => ok map (_ => s), new Failure(_))
+}).flatten
+
import util._
import io._
g: scala.util.Try[String]

The transform says that if the computation succeeded, convert that success to failure if the result of the close, ok, is a failure; and on a failed computation, keep that failure, though some people prefer to add up their failures.

Don't you know try/catch is so 1990s. </droll> (droll does not mean troll.)



回答4:

Or just:

val is = new FileInputStream(file)
val result = try {
    // do stuff
} finally {
    is.close()
}

Because there's no way is can be null.

In your question code, just move val out outside try block. That way it will be identical to what Java AutoCloseable does, except for null case. But in Scala you don't have to deal with nullables.



标签: scala