Scala Puzzle 3.Location, Location, Location
官网地址:http://scalapuzzlers.com/#pzzlr-004
3.Location, Location, Location
Scala为我们提供了许多简洁的特性,在许多面向对象的语言中,类构造器(class constructor)需要接受参数以赋值给类成员(class member)。
1 | class MyClass(param 1, param2, ...) { |
Scala代码允许你一口气声明成员变量和构造器参数。
1 | class MyClass(val member1 = "default_value1", val member2, ...){ |
一个简单的例子
1 | scala> class B(val p: String = "World"){ |
再深入一些,我们来观察以下代码的结果
1 | scala> trait A { |
解释
这里涉及到几个关键点:
- “Readers”赋值给
audience
是什么时刻可见(becomes visible)的 - 参数缺省值”World“在这种情况下是否会影响
audience
的取值 audience
的成员声明移动至参数列表是否会影响程序
从输出的值来看,当构造器的参数设定为”Readers”时,默认的参数值”World”不会影响audience
的取值。
剩下两个问题需要我们进一步地观察代码。
1 | class BMember(val a: String = "World") extends A { |
这两个类的声明都可以归纳成以下形式:
class c(params) extends superclass { statements}
对应的初始化语句new BMember("Readers")
和new BConstructor("Readers")
执行顺序:
求出
params
的值,这里是单纯的字符串”Readers”执行
superclass {statements}
首先执行
superclass
部分,对于这个例子,就是A的构造函数1
2
3
4trait A {
val audience: String
println("Hello " + audience)
}接下来执行
{statements}
部分,也就是子类BMember
或BConstructor
中的语句
这里我们省略了trait
的特性信息,你可以单纯地把它当作一个父类。
对于BMemeber
来说,首先将”Readers”赋值给其构造器参数a
,接着执行A
的构造器参数,此时A
中audience
还未初始化,所以执行语句 println("Hello " + audience)
输出结果为”Hello null”。这里执行完后程序回到BMember
内部的代码块,此时将构造器参数a
赋值给成员变量audience,所以执行语句 println("I repeat: Hello " + audience)
输出结果为”I repeat: Hello Readers”
对于BConstructor
来说情况略有不同,作为构造器参数求值的一部分,构造器参数”Readers”被立刻赋值给成员变量audience
,所以执行到A
中的代码块中时audience
就不再是null了。
这两种写法中,第二种显然出错的机会更少。
对于这个问题,scala官方文档解释得不错。
附上一个详细的例子说明
1 | scala> trait A { |
小结
对于有继承关系的类,特别注意其初始化可能导致的问题。
参考资料
版权声明:
除另有声明外,本博客文章均采用 知识共享(Creative Commons) 署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议 进行许可。
分享