2015-01-05 48 views
4

我嘗試在trait中使用抽象val來初始化另一個值。我有一個NullPointerException。我煮的行爲降低到最小的測試案例:如何初始化特徵的減法值?

trait MessagePrinter { 
    val message: String 
    println(message) 
} 

class HelloPrinter extends MessagePrinter { 
    val message = "Hello World" 
} 

val obj = new HelloPrinter() 
println(obj.message) 

這個小程序產生以下結果:

null 
Hello World 

我的印象是一個VAL可能永遠不會改變。這是預期的行爲還是編譯器錯誤?我怎樣才能解決此問題並在初始化期間打印Hello World

+0

可能的解決方法:'lazy val message'或'def message'。不贊成使用'delayedInit'(http://www.scala-lang.org/files/archive/nightly/docs/library/index.html#scala.DelayedInit)有類似的目的。這是一個JVM兼容性工件:http://docs.scala-lang.org/tutorials/FAQ/initialization-order.html –

+0

悲傷懶惰的vals不能抽象。 – Richard

回答

11

通過Scala specification的5.1節,超級類首先被初始化。儘管vals通常不能重新實例化,但它們的確在構建過程中以默認初始值開始。您可以使用def,它有不同的語義:

trait MessagePrinter { 
    def message: String 
    println(message) 
} 

class HelloPrinter extends MessagePrinter { 
    def message = "Hello World" 
} 

或者你可以考慮圍繞開關的東西,像這樣:

class HelloPrinter extends { val message = "Hello World" } with MessagePrinter 

在這種情況下,超類的順序進行評估,從而使MessagePrinter初始化應該按照需要工作。

2

在這兩種情況下,您都應該使用def

一個描述該行爲的來源是「Scala Puzzlers」益智遊戲4:

以下規則控制瓦爾斯的初始化和壓倒一切的行爲 :

  1. 超類的子類之前完全初始化。
  2. 成員按其聲明的順序進行初始化。
  3. 當val被覆蓋時,它仍然只能被初始化一次。
  4. 就像一個抽象的val,重寫的val在構造超類時會有一個默認的初始值。