版权声明:本文为博主原创文章,未经博主容许不得转载。java
手动码字不易,请你们尊重劳动成果,谢谢程序员
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来写代码。app
可是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函数会打印什么结果?ui
是不是你们期待的func1:64 func2:64
这个结果?this
然而并非,我运行出来的结果是这样的:
从运行结果来看,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