尽管Scala和其他函数式编程都不太鼓励使用return,但我不喜欢太多缩进的代码,所以很少用较多的逻辑嵌套(除非是那种非常对称的嵌套),而是喜欢将不满足条件的先return掉。最近遇到一个scala里的流控陷阱,即在lambda里的return背后实际是通过特定的异常来实现的。
对于流控和异常捕获之前也遇到过其他陷阱,但稍不留意仍可能重犯,比如下面这段代码:
try {
...
for (topicMeta <- resp.topicsMetadata; partMeta <- topicMeta.partitionsMetadata) {
if (topicMeta.topic == p.topic && partMeta.partitionId == p.partition) {
redisClient.hset(key, field, node.host + ":" + node.port)
return
}
}
} catch {
case e: Throwable =>
...
}
它是一段在actor里的逻辑,因为不希望非预期异常导致这个actor重启,所以是对Throwable
进行的捕获,然而运行时竟捕获到了scala.runtime.NonLocalReturnControl$mcV$sp
这样的异常。for语法糖容易让人忘记它里面的操作可能是一段匿名函数,简化一下这个例子:
➜ cat Test.scala
object Test {
def main(args: Array[String]) {
val list = List("A", "B", "C")
for (e1 <- list) {
if (e1 == "C") {
println("ok, do something.")
return
}
}
}
}
看看它编译的中间环节:
➜ scalac -Xprint:explicitouter Test.scala
[[syntax trees at end of explicitouter]] // Test.scala
package <empty> {
object Test extends Object {
def <init>(): Test.type = {
Test.super.<init>();
()
};
def main(args: Array[String]): Unit = {
<synthetic> val nonLocalReturnKey1: Object = new Object();
try {
val list: List[String] = immutable.this.List.apply[String](scala.this.Predef.wrapRefArray[String](Array[String]{"A", "B", "C"}));
list.foreach[Unit]({
@SerialVersionUID(value = 0) final <synthetic> class $anonfun extends scala.runtime.AbstractFunction1[String,Unit] with Serializable {
def <init>(): <$anon: String => Unit> = {
$anonfun.super.<init>();
()
};
final def apply(e1: String): Unit = if (e1.==("C"))
{
scala.this.Predef.println("ok, do something.");
throw new scala.runtime.NonLocalReturnControl$mcV$sp(nonLocalReturnKey1, ())
}
else
()
};
(new <$anon: String => Unit>(): String => Unit)
})
} catch {
case (ex @ (_: scala.runtime.NonLocalReturnControl[Unit @unchecked])) => if (ex.key().eq(nonLocalReturnKey1))
ex.value$mcV$sp()
else
throw ex
}
}
}
}
很明显return
在嵌套的匿名函数里是无法跳出外层函数的,所以编译器通过抛出 scala.runtime.NonLocalReturnControl
异常来实现跳出最外层。所有的lambda中使用return都是如此:
def main(args: Array[String]) {
val lambda: String => Unit =
(str: String) => { if ("hit" == str) return } //跳出main
try {
lambda("hit")
} catch {
case e: Throwable => //scala.runtime.NonLocalReturnControl
e.printStackTrace()
}
}
还是要注意对Throwable
的捕获,不要干扰流控逻辑:
try{
...
}catch {
case e0: ControlThrowable => throw e0 // 不要干预流控的异常
case e1: Throwable => e1.printStackTrace
}