慎用Scala中的return表达式

慎用Scala中的return表达式

版权声明:本文为博主原创文章,未经博主允许不得转载。

手动码字不易,请大家尊重劳动成果,谢谢

作者: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来写代码。

但是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函数会打印什么结果?

是否是大家期待的func1:64 func2:64这个结果?

然而并不是,我运行出来的结果是这样的:

这里写图片描述

从运行结果来看,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

posted @ 2018-05-07 21:30  海角Q  阅读(392)  评论(0编辑  收藏  举报