I'm unhappy with the 开发者_JAVA技巧rule about variable scope in a try block not being shared with associated catch and finally blocks. Specifically it leads to code like the following:
var v: VType = null
try {
v = new VType()
}
catch {
case e => // handle VType constructor failure (can reference v)
}
finally {
// can reference v.
}
As opposed to:
try {
val v = new VType()
}
catch {
case e => // handle VType constructor failure (can reference v)
}
finally {
// can reference v.
}
Can anyone please explain or justify why this rule from Java persists?
and / or is there hope that this could change?
Thanks!
UPDATE
Many thanks for all the responses to date.
The consensus seems to imply "just get on with it" and I'm starting to conclude that perhaps technically what I want is either unsound, not worth the effort or hard to achieve.
I like Rex Kerr's answer but how would the original code above be wrapped in a method call without introducing a local var in the method body?
My own efforts weren't too good, using a by-name parameter to delay construction until safely in the try block works but still doesn't give me access to the constructed (or not) object in the catch or finally blocks.
Just "try" this ;)
val v = try { new VType() } catch { case e: Exception => /* ... */ }
In Scala, try
is an expression, so it has a value.
You might be thinking about the problem the wrong way. Why do you want so much stuff in your try/catch/finally block? In your code,
try { val v = new VType() }
the exception could be thrown before you get v
back, so you can't safely reference v
. But if you can't reference v
, then what can you do on the finally side that won't break or throw its own exception or have some other ill-defined behavior? What if you create v
but fail to create w
, but disposal requires having w
as well? (Or doesn't?) It ends up being a mess.
But if you're coming from Java, there are a few things that can help you write try/catch/finally blocks in a sensible way.
One thing you can do is catch certain classes of exceptions and turn them into options instead:
def s2a(s: String) = try { Some(s.toInt) } catch { case nfe: NumberFormatException => None}
Another thing you can do is to create your own resource manager
def enclosed[C <: { def close() }](c: C)(f: C => Unit) {
try { f(c) } finally { c.close() }
}
enclosed(new FileInputStream(myFile))(fis => {
fis.read...
}
Or you can create your own shut-down-and-escape-safely method within another method:
val r = valuableOpenResource()
def attempt[F](f: => F) = {
try { f } catch { case re: ReasonableException => r.close() throw re }
}
doSomethingSafe()
attempt( doSomethingDangerous() )
doSomethingElseSafe()
r.close()
Between these different ways of handling things, I've not had much need to create vars to hold variables that I want to clean up later or otherwise deal with in catch or finally blocks.
How would this code work?
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.
}
What are the values of i, x, and y? Did y even get declared before we landed in the catch block?
The exception concept is not a subroutine of the try block, it is an alternate code flow. That makes at try-catch control block more like an "if anything untoward happens" then insert these (catch) lines in the current position of the try block, as needed.
With that in mind, it isn't clear if Val v = Type();
is going to be defined or not because the exception could (theoretically) be thrown before Val v = Type();
is evaluated. Yes, Val v
is the first line in the block, but there are JVM errors which could be thrown before it.
Finally is another code construct which adds and alternate, but required, code flow the end of leaving the try-catch construct. Again, we have no idea how much (if any) of the try block was evaluated before the finally
block was called, so we cannot depend on the declared variables within that block.
The only alternative left (now that we cannot use try block variables, due to their uncertainty of existence) is to use variable outside of the entire try-catch-finally construct for communications between the individual code blocks.
Does it suck? Maybe a little. Do we have anything that's better? Probably not. Putting the variable declarations outside of the block makes it obvious that the variables will have been defined prior to whatever control structure you process through in a try-catch-finally scenario.
If your main concern is that v
should be immutable, you might get close to what you want with:
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
}
where LazyVal
is defined as
/**
* 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()
}
It would be nice if we could use lazy val v = new VType()
, but AFAIK there is no mechanism to safely determine whether a lazy val
has been initialized.
Here's another alternative:
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}
}
}
You can use it like this:
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
}
which results in
Adding Set
Closing
Closed File
Closed File
java.lang.Exception: BadFile failed
So the first two files are created, then when the third constructor fails, the first two are automatically closed. All files are values.
Your example does not concretize why you need the finally clause. If VType is e.g. a resource that needs do be closed, you could do it one of the following ways.
1) You want to reference v after using it throws an exception:
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) You don't care about v in the catch clause:
try {
val v = new VType // may throw
try {
v.someThing // may throw
}
finally {
v.close()
}
}
catch {
case ex => println("Error on either operation: " + ex)
}
In either case, you get rid of the var.
精彩评论