版權聲明:本文爲博主原創文章,未經博主容許不得轉載。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