![](/img/trans.png)
[英]Scala: val foo = (arg: Type) => {…} vs. def(arg:Type) = {…}
[英]scala inheritance issue: val vs. def
從Odersky的書中寫一個簡單的例子導致了以下問題:
// AbstractElement.scala
abstract class AbstractElement {
val contents: Array[String]
val height: Int = contents.length // line 3
}
class UnifiedElement(ch: Char, _width: Int, _height: Int) extends AbstractElement { // line 6
val contents = Array.fill(_height)(ch.toString() * _width)
}
object AbstractElement {
def create(ch: Char): AbstractElement = {
new UnifiedElement(ch, 1, 1) // line 12
}
}
,
// ElementApp.scala
import AbstractElement.create
object ElementApp {
def main(args: Array[String]): Unit = {
val e1 = create(' ') // line 6
println(e1.height)
}
}
編譯器拋出以下跟蹤:
Exception in thread "main" java.lang.NullPointerException
at AbstractElement.<init>(AbstractElement.scala:3)
at UnifiedElement.<init>(AbstractElement.scala:6)
at AbstractElement$.create(AbstractElement.scala:12)
at ElementApp$.main(ElementApp.scala:6)
at ElementApp.main(ElementApp.scala)
所以編譯器認為內容仍為null,但我在UnifiedContainer中定義了它!
當我用def和evrth替換val完美時,事情變得更加奇怪!
你能否解釋一下這種行為?
這是Paul P的一篇很棒的文章,它解釋了Scala中的初始化順序錯綜復雜。 根據經驗,你絕不應該使用抽象的val
。 始終使用抽象def
S和lazy val
秒。
在AbstractElement
的定義中,您實際上正在定義一個構造函數,該構造函數將內容初始化為null並計算contents.length。 UnifiedElement
的構造UnifiedElement
調用AbstractElement
的構造函數,然后才初始化內容。
編輯 :換句話說,我們有一個已經存在於Java(和任何OOP語言)中的問題的新實例:超類的構造函數調用在子類中實現的方法,但后者不能安全地調用,因為子類不是尚未構建。 抽象vals只是觸發它的方法之一。
這里最簡單的解決方案是將height
設為def
,這是更好的anwyay,並且要注意在另一個答案中鏈接的初始化規則。
abstract class AbstractElement {
val contents: Array[String]
def height: Int = contents.length //Make this a def
}
相反,稍微復雜的解決方案是強制contents
在高度之前初始化,您可以使用以下語法執行此操作:
class UnifiedElement(ch: Char, _width: Int, _height: Int) extends {
val contents = Array.fill(_height)(ch.toString() * _width)
} with AbstractElement {
//...
}
請注意,mixin組合, with
對稱,不是對稱的 - 它從左到右工作。 請注意,如果您沒有定義其他成員,則可以省略最后的{}
。
延遲val也是一種解決方案,但它們會產生相當多的運行時開銷 - 每當您讀取變量時,生成的代碼將讀取易失性位圖以檢查該字段是否已初始化。
在這里制作contents
def
似乎是一個壞主意,因為它將被重新計算得太頻繁。
最后,避免抽象的val是恕我直言,這是一個極端的措施。 有時它們是正確的 - 你應該小心使用抽象vals的具體val。
編輯 :似乎不是抽象的val,而是可以使用抽象定義並用具體的val覆蓋它。 這確實是可能的,但如果有具體的val引用抽象定義則沒有用。 考慮上面代碼的這個變體,並注意如何定義成員:
abstract class AbstractElement {
def contents: Array[String]
val height: Int = contents.length // line 3
}
class UnifiedElement(ch: Char, _width: Int, _height: Int) extends AbstractElement {
val contents = Array.fill(_height)(ch.toString() * _width)
}
此代碼具有與OP給出的代碼相同的運行時行為,即使AbstractElement.contents
現在是def
:訪問者的主體讀取僅由子類構造函數初始化的字段。 抽象值和抽象定義之間的唯一區別似乎是抽象值只能被具體值覆蓋,因此如果這是您想要的,那么約束子類的行為會很有用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.