我对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块中的构造(或不)对象
答案 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。