这个例子是以前从scala-user邮件列表看到的,我借用这里例子加工了一下,这是一个for表达式
转换的细节
//有一个map,里面有一些自定义类型的数据,然后在用下面的for进行操作
for((key,value) <- m; name=key) {
println(name)
}
他在运行时发现上面的代码时发现key的hashCode会被调用,而正好这个hashCode的实现很复杂,导致效率很低。而他使用下面的写法则没有问题:
for((key,value) <- m) {
val name=key
println(name)
}
分析一下第一种写法为什么会导致hashCode方法被执行,先模拟一下:
scala> class MyKey { override def hashCode() = { println("key"); super.hashCode }}
scala> class MyValue { override def hashCode() = { println("value"); super.hashCode }}
// 把上面的k,v放入一个map;在放入Map的过程 k和v的hashCode方法都被执行了
scala> val m = Map[MyKey, MyValue](new MyKey() -> new MyValue())
key
value
// 模拟第一种for表达式
scala> for( (k,v) <- m; name = k ) { println(name+" do nothing") }
key // k的hashCode被调用
$line3.$read$$iw$$iw$MyKey@63b97fd0 do nothing
是否这里又把k放入了Map?在上面的for表达式里,没有用到yield
,是否直接被转换为foreach呢?
for表达式的展开和转换不在这里讨论,关于其语法背后的转换可以参考这篇文章。我们聚焦在这个案例的细节上。for表达式可以这样描述:
for([pattern <- generator; definition*]+; filter* )
[yield] expression
在for小括号(也可以用花括号)内部由一个或多个 p <- generator; definition*
以及0个或多个filter
组成。后跟着一个可选的yield
关键字,以及后续的逻辑表达。
把 p <- generator; definition*
这句再展开,由一个 p <- generator
以及0个或多个definition
组成。
这个问题的细节就在于有没有definition对for表达式在转换时的影响;也就是说 for(p <- generator)
与 for(p <- generator; definition)
是不一样的。
没有yield关键字,for((key,value) <- m)
直接翻译为
m.foreach(…)
而 for((key,value) <- m; name=key)
被翻译为
m.map(…).foreach(…)
为什么中间多了一次map操作?我们通过一个简单例子来看:
scala> import reflect.runtime.universe._
scala> val list = List("A","B","C")
scala> reify( for(e <- list; x=e;y=e) { println(x+y) } )
res1: reflect.runtime.universe.Expr[Unit] =
Expr[Unit]($read.list.map(((e) => {
val x = e;
val y = e;
Tuple3.apply(e, x, y)
}))(List.canBuildFrom).foreach(((x$1) => x$1: @unchecked match {
case Tuple3((e @ _), (x @ _), (y @ _)) => Predef.println(x.$plus(y))
})))
从reify的结果可以看到,对于for(e <- list; x=e; y=e)
里面的x=e
和y=e
两个definition中的变量x,y与e一同用一个tuple封装起来,即map的结果得到的是一个 List[Tuple3] 类型的数据。随后再对这个新结果进行foreach
。
所以最初的问题是因为for里面存着了一个definition,而导致转换过程多了一次map,也就是创建了一个新的Map[Tuple2],并把数据放入新Map,在这个过程导致了hashCode的调用。
小结:对于for表达式要知道背后会被转换为什么,不要滥用for的语法糖,误用导致低效率。
就喜欢这种细致入微的分析啊!
scala的里子太复杂了。
这个需要好深的功底啊,佩服
So detail
喜欢这种剖析原理的解说,赞
宏江神13年就这么犀利了!