1. 程式人生 > >Scala 覆寫抽象欄位和具體欄位

Scala 覆寫抽象欄位和具體欄位

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 語句為該型別提供了具體值。