慎用Scala中的return表达式
慎用Scala中的return表达式
版权声明:本文为博主原创文章,未经博主允许不得转载。
手动码字不易,请大家尊重劳动成果,谢谢
The return keyword is not “optional” or “inferred”; it changes the meaning of your program, and you should never use it.
由于Scala是一种基于JVM的语言,因此大多数程序员都是从Java转过来的,因此可能会习惯于使用return来写代码。
但是Scala作为一种支持函数式编程的语言,是不推荐大家使用return表达式的,下面通过几个例子来看下Scala中return可能会带来的错误。
首先先看一个例子:
object ScalaReturn{
def main(args: Array[String]): Unit = {
println("func1:" + func1())
println("func2:" + func2())
}
def func1(): Int = {
def func_inner(i: Int): Int = {
if (i == 0) return -1
return i+1
}
func_map(func_inner)
}
def func2(): Int ={
val func_Inner: Int => Int = i => {
if (i == 0) return -1
return i+1
}
func_map(func_Inner)
}
def func_map(f: Int => Int): Int = {
0 to 10 map f sum
}
}
这里我写了两个一模一样的的函数func1
与func2
,其中只有func_inner
函数的定义一点细微的差别,在func_inner
里我使用了大家常用的return表达式来返回函数结果。func_map
函数我使用了很好(nan)看(dong)的写法,其实就是把0到10这11个数字分别应用f函数,最后将函数返回值求和。
大家认为运行main函数会打印什么结果?
是否是大家期待的func1:64 func2:64
这个结果?
然而并不是,我运行出来的结果是这样的:
从运行结果来看,func1
的运行结果是符合预期的,func2
的运行结果就有些奇怪了。
为了了解问题出现的原因,我们还是求助我们的老朋友javap
:
我们先来看下func1
与func_Inner
函数的编译后的结果:
public int func1();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: new #56 // class ScalaReturn$$anonfun$func1$1
4: dup
5: invokespecial #57 // Method ScalaReturn$$anonfun$func1$1."<init>":()V
8: invokevirtual #61 // Method func_map:(Lscala/Function1;)I
11: ireturn
LocalVariableTable:
Start Length Slot Name Signature
0 12 0 this LScalaReturn$;
LineNumberTable:
line 12: 0
public final int ScalaReturn$$func_inner$1(int);
descriptor: (I)I
flags: ACC_PUBLIC, ACC_FINAL
Code:
stack=2, locals=2, args_size=2
0: iload_1
1: iconst_0
2: if_icmpne 7
5: iconst_m1
6: ireturn
7: iload_1
8: iconst_1
9: iadd
10: ireturn
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this LScalaReturn$;
0 11 1 i I
LineNumberTable:
line 9: 0
line 10: 7
StackMapTable: number_of_entries = 1
frame_type = 7 /* same */
完全中规中矩,和我们预期的完全一样。Scala对于函数内的函数就直接把它重命名到了外部(如果引用了外部变量,则会把变量增加在函数参数里),这个和Java的lambda表达式有点类似。
我们再来看下func2
内func_Inner
函数的编译后的结果:
public final scala.runtime.Nothing$ apply(int);
descriptor: (I)Lscala/runtime/Nothing$;
flags: ACC_PUBLIC, ACC_FINAL
Code:
stack=5, locals=2, args_size=2
0: iload_1
1: iconst_0
2: if_icmpne 18
5: new #23 // class scala/runtime/NonLocalReturnControl$mcI$sp
8: dup
9: aload_0
10: getfield #25 // Field nonLocalReturnKey1$1:Ljava/lang/Object;
13: iconst_m1
14: invokespecial #29 // Method scala/runtime/NonLocalReturnControl$mcI$sp."<init>":(Ljava/lang/Object;I)V
17: athrow
18: new #23 // class scala/runtime/NonLocalReturnControl$mcI$sp
21: dup
22: aload_0
23: getfield #25 // Field nonLocalReturnKey1$1:Ljava/lang/Object;
26: iload_1
27: iconst_1
28: iadd
29: invokespecial #29 // Method scala/runtime/NonLocalReturnControl$mcI$sp."<init>":(Ljava/lang/Object;I)V
32: athrow
LocalVariableTable:
Start Length Slot Name Signature
0 33 0 this LScalaReturn$$anonfun$1;
0 33 1 i I
LineNumberTable:
line 18: 0
line 19: 18
StackMapTable: number_of_entries = 1
frame_type = 18 /* same */
从上面的代码中我们可以看出,它的返回是使用throw NonLocalReturnControl$mcI$sp(nonLocalReturnKey1$1, returnValue)
来实现的,有抛出异常必定有catch异常,那就是在func2
函数里:
public int func2();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=3, locals=4, args_size=1
0: new #4 // class java/lang/Object
3: dup
4: invokespecial #64 // Method java/lang/Object."<init>":()V
7: astore_1
8: new #66 // class ScalaReturn$$anonfun$1
11: dup
12: aload_1
13: invokespecial #68 // Method ScalaReturn$$anonfun$1."<init>":(Ljava/lang/Object;)V
16: astore_3
17: aload_0
18: aload_3
19: invokevirtual #61 // Method func_map:(Lscala/Function1;)I
22: goto 38
25: astore_2
26: aload_2
27: invokevirtual #72 // Method scala/runtime/NonLocalReturnControl.key:()Ljava/lang/Object;
30: aload_1
31: if_acmpne 39
34: aload_2
35: invokevirtual #75 // Method scala/runtime/NonLocalReturnControl.value$mcI$sp:()I
38: ireturn
39: aload_2
40: athrow
Exception table:
from to target type
8 25 25 Class scala/runtime/NonLocalReturnControl
LocalVariableTable:
Start Length Slot Name Signature
0 41 0 this LScalaReturn$;
17 5 3 func_Inner Lscala/Function1;
LineNumberTable:
line 16: 0
line 17: 8
line 21: 17
line 16: 25
StackMapTable: number_of_entries = 3
frame_type = 255 /* full_frame */
offset_delta = 25
locals = [ class ScalaReturn$, class java/lang/Object ]
stack = [ class scala/runtime/NonLocalReturnControl ]
frame_type = 76 /* same_locals_1_stack_item */
stack = [ int ]
frame_type = 252 /* append */
offset_delta = 0
locals = [ class scala/runtime/NonLocalReturnControl ]
从上面func2
的代码我们可以看出其捕获了NonLocalReturnControl
异常,并且判断异常中的key是否和之前传入进去的key一致,如果一致则返回其value,否则继续将该异常抛出。
所以在fun2
的func_Inner
里,return表达式转换成了异常抛出,在外层调用点被捕获。这样就实现了Scala中的Non-Loacl Return。因此在内层函数接收到参数0
的时候,return的这个-1
直接作为了func2
函数的返回值,而不是func_Inner
的返回值。
这里我们就要问了,在什么情况下return
会被解释成抛出异常呢?,那就是return出现在非命名闭包里的时候,比如我们常见的lambda表达式里。一旦这些地方出现了return,那么它就被赋予了退出其所在的外层函数的使命,如果一直到不了外层函数并且未被捕获,那么它可能会终止你的线程。
后记:
我注意到return这个东西的原因是之前在我做一个项目的时候,我使用了Scala作为框架开发语言,在进程间交互的时候我使用了一个定时器线程来轮询另外一个接口的状态,但是偶尔我会发现这个定时器线程莫名其妙地就没有了,在日志里也看不到什么异常。之后我对定时器内的方法加上了try catch Throwable,并且把异常日志打印出来。这时候我发现了一个奇怪的异常,也就是上面说的NonLocalReturnControl,当时我也没搞清楚这是啥东西,直到最近看博客的时候才发现Scala里的return原来还有另一种语义。这才去回看之前的代码,发现确实在lambda表达式里写了return语句,原本以为只会向Java里一样退出这个lambda表达式,结果它时不时就把我的线程给杀掉了。
关于Non-local return的解释可以参考以下链接,解释的很详细:
https://www.zhihu.com/question/22240354/answer/64673094
stackoverflow中的讨论:
https://stackoverflow.com/questions/12560463/return-in-scala