Scala 覆写抽象字段和具体字段
1 覆写trait中的字段
下面列举了一段精心设计的示例代码。在字段初始化之前,该示例会调用这个尚未定义的字段:
package cn.com.tengen.test.obj
trait TestTrait {
val value: Int
val inverse = 1.0/value
}
object Test {
def main(args: Array[String]): Unit = {
val o = new TestTrait {
override val value: Int = 10
println("TestTrait: value = "+value+", inverse = "+inverse)
}
}
}
//输出:
TestTrait: value = 10, inverse = Infinity
正如你所预计的那样,inverse 变量过早被计算了。尽管没有抛出除零异常(divide-byzeroexception),但是编译器仍认为inverse 值无穷大。Scala 为此类问题提供了两个解决方案。第一个方案是使用惰性值(lazy value)
package cn.com.tengen.test.obj
trait TestTrait {
val value: Int
lazy val inverse = 1.0/value
}
object Test {
def main(args: Array[String]): Unit = {
val o = new TestTrait {
override val value: Int = 10
println("TestTrait: value = "+value+", inverse = "+inverse)
}
}
}
//输出:
TestTrait: value = 10, inverse = 0.1
现在,inverse 成功地被初始化,并被赋予了合法值,只有在调用时lazy 关键字才能起到作用。这是因为lazy 会推迟对变量进行估值,直到有代码需要使用该值。假如某一val 变量是惰性值,请确保尽可能地推迟对该val 值的使用。
第二个方案:预先初始化字段
trait TestTrait {
val value: Int
val inverse = 1.0/value
println("TestTrait: value = "+value+", inverse = "+inverse)
}
object Test {
def main(args: Array[String]): Unit = {
val o = new {
val value: Int = 10
} with TestTrait
}
}
//输出:
TestTrait: value = 10, inverse = 0.1
在with TestTrait 子句执行之前,我们便已实例化了一个匿名内部类并在代码块中初始化了该内部类的值字段。这确保了在执行TestTrait 特征体之前,value 字段已初始化完毕。
尽可能避免(在类中和trait 中)使用var 字段,而使用公共 var 字段则尤为危险。
不过,val 所提供的可见性保护并非无懈可击。我们可以在初始化子类实例时对trait 中的val 字段进行覆写,不过初始化完之后,该字段仍为不可变值。
2 覆写类字段
类中声明的成员,其表现与trait 中的成员大致相同。为了能够完整地描述如何覆写类字段,下面这个示例中的继承类既覆写了val 字段,又对var 字段进行了重新赋值:
package cn.com.tengen.test.obj
class Test {
val name = "Test"
var count = 0
}
class TestSon extends Test {
override val name = "TestSon"
count = 1
}
object Test {
def main(args: Array[String]): Unit = {
val s = new TestSon()
println(s.name)
println(s.count)
}
}
//输出:
TestSon
1
覆写具体val 字段时,override 关键字是必不可少的,不过对于count 这个var 字段而言,则不然。这是因为修改val 字段意味着我们正在修改某一常量(val 字段)的初始化过程,这类“特殊”操作需要使用override 关键字。
正如我们所预计的那样,继承类对这两个字段都进行了覆写。下面对之前的示例进行修改,将基类中的val 字段和var 字段都修改成abstract 字段:
package cn.com.tengen.test.obj
abstract class Test {
val name:String
var count:Int
}
class TestSon extends Test {
val name = "TestSon"
var count = 1
}
object Test {
def main(args: Array[String]): Unit = {
val s = new TestSon()
println(s.name)
println(s.count)
}
}
由于这些字段均声明为abstract 类型,因此TestSon中不再需要override 关键字。但是var 和 val不能少
有必要强调一下:name 和count 是抽象字段,它们并不是包含默认值的具体字段。如果在Java 类中进行类似的声明,如String name,Java 将会声明一个具有默认值的具体字段,而在本例中默认值为null。Java 并不支持抽象字段,只支持抽象方法。
3 覆写抽象类型
abstract class Test {
type In
val source: In
def read: String // 该方法会读取数据源内容,并返回字符串
}
class TestSon(val source: String) extends Test {
type In = String
def read: String = source
}
该示例演示了如何声明抽象类型以及如何在继承类中为该抽象类型定义具体值。Test 类声明了In 类型,但却未初始化该类型。而具体继承类TestSon使用type In = String 语句为该类型提供了具体值。