簡體   English   中英

scala繼承問題:val與def

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM