尝试阻止范围

时间:2010-08-27 20:03:47

标签: scala scope try-catch

我对try块中的变量作用域规则没有与关联的catch和finally块共享感到不满意。具体来说,它会导致代码如下:

var v: VType = null

try {
  v = new VType()
}
catch {
  case e => // handle VType constructor failure (can reference v)
}
finally {
  // can reference v.
}

相反:

try {
  val v = new VType()
}
catch {
  case e => // handle VType constructor failure (can reference v)
}
finally {
  // can reference v.
}

有人可以解释或证明为什么Java中的这条规则仍然存在吗?

和/或希望这可能会改变吗?

谢谢!

更新

非常感谢迄今为止的所有回复。

这种共识似乎意味着“只是继续下去”而且我开始得出结论,或许在技术上我想要的是不健全,不值得努力或难以实现。

我喜欢Rex Kerr的答案,但如果不在方法体中引入局部变量,上面的原始代码将如何包装在方法调用中?

我自己的努力并不太好,使用by-name参数来延迟构造,直到在try块中安全地工作,但仍然不允许我访问catch或finally块中的构造(或不)对象

7 个答案:

答案 0 :(得分:20)

只需“试试”这个;)

val v = try { new VType() } catch { case e: Exception => /* ... */ }

在Scala中,try是一个表达式,因此它有一个值。

答案 1 :(得分:14)

你可能会以错误的方式思考问题。为什么你在try / catch / finally块中需要这么多东西?在您的代码中,

try { val v = new VType() }

在您v返回之前,可能会抛出异常,因此您无法安全地引用v。但是如果你不能引用v,那么你可以在最终方面做些什么,不会破坏或抛出自己的异常或者有其他一些不明确的行为?如果您创建v但未能创建w该怎么办,但处置也需要w? (或者不是?)最终变得一团糟。

但是如果你来自Java,有一些东西可以帮助你以合理的方式编写try / catch / finally块。

您可以做的一件事是捕获某些类别的异常并将其转换为选项:

def s2a(s: String) = try { Some(s.toInt) } catch { case nfe: NumberFormatException => None}

您可以做的另一件事是创建自己的资源管理器

def enclosed[C <: { def close() }](c: C)(f: C => Unit) {
  try { f(c) } finally { c.close() }
}
enclosed(new FileInputStream(myFile))(fis => {
  fis.read...
}

或者您可以在另一种方法中创建自己的关闭并安全转义方法:

val r = valuableOpenResource()
def attempt[F](f: => F) = {
  try { f } catch { case re: ReasonableException => r.close() throw re }
}
doSomethingSafe()
attempt( doSomethingDangerous() )
doSomethingElseSafe()
r.close()

在这些处理事物的不同方式之间,我没有太多需要创建变量来保存我想要稍后清理的变量,或者以catch或finally块来处理。

答案 2 :(得分:6)

这段代码如何运作?

try
{
    int i = 0;

    // Do stuff...

    Foo x = new Foo();

    // Do more stuff...

    Bar y = new Bar();
}
catch
{
    // Print the values of i, x, and y.
}

i,x和y的值是多少?在我们降落到捕获区之前,你甚至被宣布了吗?

答案 3 :(得分:4)

异常概念不是try块的子例程,它是备用代码流。这使得try-catch控制块更像是“如果发生任何不幸事件”,然后根据需要在try块的当前位置插入这些(catch)行。

考虑到这一点,不清楚是否要定义Val v = Type();,因为在Val v = Type();被评估之前可以(理论上)抛出异常。是的,Val v是块中的第一行,但是在它之前可能会抛出JVM错误。

最后是另一个代码构造,它在离开try-catch构造的末尾添加并交替但需要代码流。同样,我们不知道在调用finally块之前评估了多少(如果有的话)try块,所以我们不能依赖于该块中声明的变量。

剩下的唯一选择(现在我们不能使用try块变量,因为它们存在的不确定性)是在整个try-catch-finally构造之外使用变量来进行各个代码块之间的通信。

难道吗?或许一点点。我们有什么更好的吗?可能不是。将变量声明置于块之外使得很明显变量将在try-catch-finally场景中处理的任何控制结构之前定义。

答案 4 :(得分:3)

如果您主要担心v应该是不可变的,那么您可能会接近您想要的内容:

case class VType(name: String) { 
   // ... maybe throw an exception ...
}

val v = LazyVal(() => new VType())
try {
   // do stuff with v
   println(v.name) // implicitly converts LazyVal[VType] to VType

   // do other unsafe stuff
} catch {
   case e => // handle VType constructor failure
   // can reference v after verifying v.isInitialized
} finally {
   // can reference v after verifying v.isInitialized
   if (v.isInitialized) v.safelyReleaseResources
}

其中LazyVal定义为

/**
 * Based on DelayedLazyVal in the standard library
 */
class LazyVal[T](f: () => T) {
   @volatile private[this] var _inited = false
   private[this] lazy val complete = {
      val v = f()
      _inited = true
      v
   }

   /** Whether the computation is complete.
    *
    *  @return true if the computation is complete.
    */
   def isInitialized = _inited

   /** The result of f().
    *
    *  @return the result
    */
   def apply(): T = complete
}

object LazyVal {
   def apply[T](f: () => T) = new LazyVal(f)
   implicit def lazyval2val[T](l: LazyVal[T]): T = l()
}

如果我们可以使用lazy val v = new VType()会很好,但AFAIK没有机制可以安全地确定lazy val是否已初始化。

答案 5 :(得分:3)

这是另一种选择:

object Guard {
    type Closing = {def close:Unit}

    var guarded: Stack[Set[Closing]] = Stack()
    def unapply(c: Closing) = {
      guarded.push(guarded.pop + c)
      Some(c)
    }

    private def close {println("Closing"); guarded.head.foreach{c => c.close}}
    private def down {println("Adding Set"); guarded.push(Set())}
    private def up {println("Removing Set"); guarded.pop}

    def carefully(f: => Unit) {
      down
      try {f}
      finally {close; up}
    }
}

你可以像这样使用它:

import Guard.carefully

class File {def close {println("Closed File")}}
class BadFile {def close {println("Closed Bad File")}; throw new Exception("BadFile failed")}

carefully {
  val Guard(f) = new File
  val Guard(g) = new File
  val Guard(h) = new BadFile
}

导致

  

添加集

     

结束

     

已关闭文件

     

已关闭文件

     

java.lang.Exception:BadFile失败

因此创建了前两个文件,然后当第三个构造函数失败时,前两个文件会自动关闭。所有文件都是值。

答案 6 :(得分:2)

您的示例没有具体说明您需要finally子句的原因。如果VType是例如需要关闭的资源,您可以通过以下方式之一来完成。

1)你想在使用后引用v引发异常:

try {
  val v = new VType // may throw
  try {
    v.someThing  // may throw
  }
  catch {
    case ex => println("Error on doing something with v :" + v + ex) // or whatever
  }
  finally {
    v.close()
  }
}
catch {
  case ex => println("Error on getting or closing v: " + ex)  // v might not be constructed
}

2)你不关心catch子句中的v:

try {
  val v = new VType // may throw
  try {
    v.someThing  // may throw
  }
  finally {
    v.close()
  }
}
catch {
  case ex => println("Error on either operation: " + ex)
}

在任何一种情况下,你都摆脱了var。