非你所想Scala@我不是你想的那样的匿名函数

Scala 非常重视代码的简明扼要,形如arg => expr的匿名函数, 可以很明显的体现函数的结构体,也可以用在一些含有多条语句的复杂函数上。
对于自解释参数名的函数,我们更优雅的选择是使用占位符,以避免对参数进行声明,例如

List(1, 2).map { i => i + 1 }

可以简化成 :

List(1, 2).map { _ + 1 } 

这两个表达式是等价的,在REPL里执行的结果也一样,如果我们加入一段debug语句,来看看表达式的运行过程将会如何呢?

List(1, 2).map { i => println("hi"); i + 1 }
List(1, 2).map { println("hi); _+ 1 }

让我们来猜测一下,这两段代码运行的结果将会如何。。。

1. 第一种可能,程序将打印:
   Hi
   List[Int] = List(2, 3)
   Hi
   List[Int] = List(2, 3)
2. 第二种可能,程序将打印:
    Hi
    Hi
    List[Int] = List(2, 3)
    Hi
    Hi
    List[Int] = List(2, 3)
3. 第三种可能,程序将打印:
    Hi
    List[Int] = List(2, 3)
    Hi
    Hi
    List[Int] = List(2, 3)
4. 第四种可能:
    第一段代码打印
    Hi
    Hi
    List[Int] = List(2, 3)
    第二段程序将报编译错误

你已经选好了答案, 现在我们把代码输入REPL,看看运行结果是不是和预想的一致, coding。。。 enter。。。
Author:mark

scala> List(1, 2).map { i => println("Hi"); i + 1 }
    Hi
    Hi
    res23: List[Int] = List(2, 3)
scala> List(1, 2).map { println("Hi"); _ + 1 }
    Hi
    res25: List[Int] = List(2, 3)

此刻,我心中有一万头草泥马在蹦腾, why, why,why ! 我们只是用占位符简化了表达式的写法,但是程序运行逻辑怎么就不一样了呢?
因为匿名函数经常作为参数传递,通常情况下都是以{}包围起来,顺其自然,我们会认为在一对大括号内的代码就是一整段函数体, 但实际上这个只是为了分隔代码块,一行或者多行语句,最终决定了这个代码块的结果。
这两个代码块如何被解析将决定程序执行的行为,{ i => println(“Hi”); i + 1 } 遵守形式 arg => exprprintln(“Hi”); i + 1 属于同一段代码块,所以println语句是方法体的一部分,方法每调用一次,println语句就将执行一次。

scala> val printAndAddOne =
(i: Int) => { println("Hi"); i + 1 }
printAndAddOne: Int => Int = <function1>
scala> List(1, 2).map(printAndAddOne)
Hi
Hi
res29: List[Int] = List(2, 3)

但是第二段语句,代码块表示的两个表达式:println(“Hi”)_+1,并且 _+1map()函数的输出结果,println并不是函数体的一部分,只有在给map赋值时会执行。

scala> val printAndReturnAFunc =
{ println("Hi"); ( _: Int ) + 1 }
Hi
printAndReturnAFunc: Int => Int = <function1>
scala> List(1, 2).map(printAndReturnAFunc)
res30: List[Int] = List(2, 3)

上面的说的一定满足不了喜欢刨根问底的人,show me the turth,写到这里,必须得拿出点儿证据。scala 代码最终会编译成class文件,用反编译工具,我们可以看到编译器理解的代码。

public final void delayedEndpoint$anonymous$AnonymousFunction$1()
  {
    Predef..MODULE$.println("hi");List..MODULE$.apply(Predef..MODULE$.wrapIntArray(new int[] { 1, 2 })).map(new AbstractFunction1.mcII.sp()
    {
      public static final long serialVersionUID = 0L;     
      public int apply$mcII$sp(int x$1)
      {
        return x$1 + 1;
      }

      public final int apply(int x$1)
      {
        return apply$mcII$sp(x$1);
      }
    }, List..MODULE$.canBuildFrom());
  }  

忽略掉其他无关信息,我们可以清楚的看到Predef..MODULE$.println(“hi”); 这段语句在方法执行体之外,这样也就验证了为什么只会打印一次。
scala鼓励我们使用精简的表达式,但是在使用占位符简化表达式时,我们需要清楚我们在干什么,避免出现一些奇奇怪怪的bug。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值