Scala - 有关变量赋值的问题

先看个小问题

先贴下一段Scala代码,看下这段代码是否存在问题?

1
2
3
4
5
val persons = List[Person](Person("tom"), Person("marry"), null).iterator
var person: Person = null
while ((person = persons.next()) != null) {
println("obj name: " + person.name)
}

如果你的答案是这段代码运行不会出任何问题的话,那么你对于 Scala 的变量赋值还是了解太少。


为什么呢

在我们一般的认知中,在 Java 和 C++ 中对变量赋值后,其会返回相对应该变量的值,而在 Scala 中,如果对变量赋值后,获取到的返回值却统一是 Unit。

Unit 是表示为无值,其作用与其他语言中的 void 作用相同,用作不返回任何结果的方法的结果类型。

回到刚才那段代码,根据以上说明,如果我们在赋值对person变量的话,那就会导致在每一次循环当中,其实我们一直都是拿 Unit 这个值去与 null 比较,那么就可以换做一个恒等式为Unit != null,这样做的结果就是这个循环不会中断。

在 IDEA 中,如果我们仔细查看代码,发现 IDE 已经提醒我们这个问题的存在了,这这也仅仅只是 Warning 而已。

若通过编译的方法查看源代码的话,会在编译的过程中,获得这样一句警告(并非错误!):

有个简单的例子可以检验自己是否明白懂了这个”bug”:

1
2
3
var a: Int = 0
var b: Int = 0
a = b = 1 // 这行代码能够跑通,在其他语言呢?

解决方案

在给出常见的解决方案前,先给出为什么 Scala 要这样设计的理由(Scala 之父亲自解释):

https://stackoverflow.com/questions/1998724/what-is-the-motivation-for-scala-assignment-evaluating-to-unit-rather-than-the-v

常见的解决方案会有以下几种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// solution 1 - 封装成代码块返回最终值,直观但麻烦
var person = null
while ({person = persons.next; person != null}) {
println("obj name: " + person.name)
}

// solution 2 (推荐)- 通过 Scala 的语法特性,使用它的奇淫技巧
Iterator.continually(persons.next())
.takeWhile(_ != null)
.foreach(t => {println("obj name: " + t.name)})

// solution 3 - 这个与 Solution2 的区别仅仅在于使用的类不同,但使用的类不同便意味着这两者之间存在着不同的遍历方式。两者的区别会在博客中更新。
Stream.continually(persons.next())
.takeWhile(_ != null)
.foreach(t => {println("obj name: " + t.name)})

参考资料:

  1. https://stackoverflow.com/questions/6881384/why-do-i-get-a-will-always-yield-true-warning-when-translating-the-following-f
  2. https://stackoverflow.com/questions/3062804/scala-unit-type
  3. https://stackoverflow.com/questions/2442318/how-would-i-express-a-chained-assignment-in-scala