我有以下Scala计划:
object FutureMapTest extends App {
println("start")
val f: Future[Long] = Future {
Thread.sleep(2000)
val x = 1
println(s"started with ${x}")
x
}
f.map { i =>
println(s"mapped to ${i*2}")
}
f.map {
val nothing = "nothing"
println(s"mapped to ${nothing}")
_ * 2
}
Thread.sleep(3000)
println("end")
}
我希望它能在控制台上打印
start
started with 1
然后(以任何顺序):
mapped to 2
mapped to nothing
接着是
end
它实际印刷的是:
start
mapped to nothing
started with 1
mapped to 2
end
所以,它似乎是第二个"地图"块立即执行,无需等待原始未来完成。怎么可能?
你甚至可以从原来的未来块中删除Thread.sleep(),结果仍然是相同的。
答案 0 :(得分:4)
这里有几个混乱的来源。
此:
f.map {
val nothing = "nothing"
println(s"mapped to ${nothing}")
_ * 2
}
扩展为:
f.map {
val nothing = "nothing"
println(s"mapped to ${nothing}")
i => i * 2
}
这是什么意思?对于某些Future#map
,A => B
需要Future[A]
的函数参数。表达式:
val nothing = "nothing"
println(s"mapped to ${nothing}")
i => i * 2
..评估到Long => Long
,但是val赋值和println
首先计算 ,因为它们是返回函数的表达式的一部分。在i => i * 2
完成之前,f
不会被执行。这类似于(Scala puzzler 001):
scala> List(1, 2, 3) map {
| val a = 1 // this only happens once, not three times
| i => a + i + 1
| }
res0: List[Int] = List(3, 4, 5)
将其更改为此将显示您期望的行为(现在val赋值和println
是函数体的一部分):
f.map { i =>
val nothing = "nothing"
println(s"mapped to ${nothing}")
i * 2
}
以下是另一种观察方式:
f.map {
println("evaluated immediately")
i => { println("evaluated after f"); i * 2 }
}