慎用Scala中的return表達式

慎用Scala中的return表達式

版權聲明:本文爲博主原創文章,未經博主容許不得轉載。java

手動碼字不易,請你們尊重勞動成果,謝謝程序員

做者:http://blog.csdn.net/wang_wbq編程

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
  }
}

這裏我寫了兩個如出一轍的的函數func1func2,其中只有func_inner函數的定義一點細微的差異,在func_inner裏我使用了你們經常使用的return表達式來返回函數結果。func_map函數我使用了很好(nan)看(dong)的寫法,其實就是把0到10這11個數字分別應用f函數,最後將函數返回值求和。函數

你們認爲運行main函數會打印什麼結果?ui

是不是你們期待的func1:64 func2:64這個結果?this

然而並非,我運行出來的結果是這樣的:

這裏寫圖片描述

從運行結果來看,func1的運行結果是符合預期的,func2的運行結果就有些奇怪了。

爲了瞭解問題出現的緣由,咱們仍是求助咱們的老朋友javap

咱們先來看下func1func_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表達式有點相似。

咱們再來看下func2func_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,不然繼續將該異常拋出。

因此在fun2func_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

相關文章
相關標籤/搜索