【问题标题】:Using a variable in finally block在 finally 块中使用变量
【发布时间】:2013-07-13 06:25:14
【问题描述】:

这是 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
   //}
  }

finally 块中有“out.close”会更好,不是吗?但我不想使用 var。

我的问题是,我该如何实现?

【问题讨论】:

    标签: scala


    【解决方案1】:

    在块中定义的变量是该块的本地变量。因此,如果您坚持手动使用 try/finally,则必须将 val 移出块。

    但是,您要实现的是创建资源,在块中使用它,并在离开块时调用它的关闭方法,无论您是正常离开块还是通过异常异常离开块。这是一个非常常见的问题,因此已经有一个库可以解决它,称为Scala ARM。 ARM 代表自动资源管理。

    基本用法如下:

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

    有人谈到将这个结构移到 scala 标准库中,所以将来您可能甚至不需要外部依赖项。

    我建议使用库来完成类似的事情。它只是你的 build.sbt 中的另一行。但出于教育目的,以下是您自己的滚动方式:

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

    这里是如何使用它

    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)
    ...
    

    你仍然会得到异常,但是 close 会被调用。当然,如果 close 也抛出异常,这将隐藏原始异常。像这样的小细节可能在 scala ARM 中处理得更好。

    【讨论】:

      【解决方案2】:

      这是我用来管理传递给函数返回而不是重新调整期货的可关闭资源

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

      我使用commons-io 中的IOUtils 来简化实际关闭资源的调用。一个简单的try { closable.close() } catch { case _ =&gt; /* blah */ } 就可以了

      示例用法:

      withClosable(new FileInpustream("f")) { stream =&gt; /* read the stream */ }

      【讨论】:

        【解决方案3】:

        对于这个用例来说,贷款模式更为常见,但由于 Stack Overflow 上会发生任何事情,您可以使用 Try 构建您要查找的表达式。

        Try 作为方便的工具值得更多曝光。

        scala> import util._
        import util._
        
        scala> import io._
        import io._
        

        尝试打开文件--

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

        然后用它做点什么,用 i/o 源将结果打包成一个元组—— 请注意,当您在 for-comprehension 中进行值定义时,它就是这样做的 --

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

        然后关闭源并产生计算结果--

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

        未注释:

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

        用一些经典的幽默文本创建测试文件,然后再试一次:

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

        您可以将其粉饰为便于理解,但请注意额外的扁平化,因为您从产量中获得了地图而不是 flatMap:

        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.)
        

        如果关闭失败我们想失败怎么办?

        我不想再在 REPL 中输入所有这些内容!

        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]
        

        transform 表示如果计算成功,如果关闭结果ok 失败,则将该成功转换为失败;如果计算失败,请保留该失败,尽管有些人更喜欢将他们的失败相加。

        你不知道 try/catch 是如此的 1990 年代。 (droll 不代表巨魔。)

        【讨论】:

        • 你能不能让它更具可读性?我不明白写的是什么。
        • @Grienders 当然,如果有帮助的话。你是指自然语言还是代码?
        【解决方案4】:

        或者只是:

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

        因为is 不可能为空。

        在您的问题代码中,只需将 val out 移到 try 块之外。这样,它将与 Java AutoCloseable 所做的相同,但 null 情况除外。但在 Scala 中,您不必处理可空对象。

        【讨论】:

        • 如果val out = ...出现异常怎么办?
        • 那么out 是未分配的,所以无论如何也不能关闭它。
        • 这是我的问题 - 如何做到这一点。
        • 不不不,如果在val out = new FileAnything() 行发生异常,您不应该(也不能)调用close()。在 Java 中也是如此。
        • 只有在println() 发生异常时才应调用close()(在我的示例中为// do stuff)。在 Java 中都是一样的,但 Java 也处理 null 的情况。但是由于我们使用了new FileSomething(),所以它不可能为空。 new 运算符不能返回 null。
        猜你喜欢
        • 2021-03-02
        • 1970-01-01
        • 2022-01-24
        • 2014-12-13
        • 2013-08-31
        • 2014-05-16
        • 1970-01-01
        • 2013-05-16
        • 2013-12-16
        相关资源
        最近更新 更多