Scala學習(八)繼承

1.擴展類(繼承)

Scala擴展類的方式和Java同樣,使用extends關鍵字:java

class Employee extends Person{
    var salary = 0.0
    ...
}

基本上,Scala中的繼承和Java沒有什麼區別,惟一的區別就是:數組

和Java同樣,你能夠將類聲明爲final,這樣它就不能被繼承。你還能夠將單個方法或字段聲明爲final,以確保他們不能被重寫。注意這裏和Java不一樣,在Java中final字段是不可變的,在Scala中時val。安全

2.重寫方法

在Scala中重寫一個非抽象的方法必須使用override修飾符。例如:併發

public class Person{
    ...
    override def toString = getClass.getName + "[name=" + name + "]"
}

在Scala中調用父類的方法和Java徹底同樣,使用super關鍵字:ide

public class Employee extends Person{
    ...
    override def toString = super.toString + "[salary=" + salary + "]"
}

3.類型檢查和轉換

要測試某個對象是否屬於某個給定的類,能夠用isInstanceOf方法。若是測試成功,你能夠用asInstanceOf方法將引用轉換爲子類引用:函數

if(p.isInstanceOf[Employee]){
    val s = p.asInstanceOf[Employee] //s的類型爲Employee
}

------------------------------------------------學習

若是你想要測試p指向的是一個Employee對象但又不是其子類的話,能夠用:測試

//這種判斷是精確判斷,==號兩邊必須徹底同樣。isInstanceOf方法是模糊判斷只要兩邊存在繼承關係就能夠返回trueflex

if(p.getClass == classOf[Employee]) 

說明:getClass方法會返回引用對象的類型及其子類類型Class[_ <:A]。classOf方法獲得的是指定對象類型Class[A]。this

說明:這裏總結下Scala中的類型界定:

  1. 協變 [+T], covariant (or 「flexible」) in its type parameter T,相似Java中的(? extends T), 便可以用T和T的子類來替換T,里氏替換原則。
  2. 不變 不支持T的子類或者父類,只知支持T自己。
  3. 逆變 [-T], contravariant, 相似(? supers T) 只能用T的父類來替換T。是逆里氏替換原則。
  4. 上界: 只容許T的超類U來替換T。 [U >: T]
  5. 下界: 只容許T的子類U來替代T。 [U <: T]

-----------------------------------------------

Scala和Java中類型檢查和轉換

Scala Java
obj.isInstanceOf[Cl] obj instanceof Cl
obj.asInstanceOf[Cl] (Cl) obj
classOf[Cl] Cl.class

4.受保護的字段和方法

和java或C++同樣,你能夠將字段或方法聲明爲protected。這樣的成員能夠被任何子類訪問,但不能從其餘位置看到。

與Java不一樣,protected的成員對於類所屬的包而言,是不可見的。

Scala還提供了一個protected[this]的變體,將訪問權限定在當前的對象,相似private[this]。

5.超類的構造

以前的學習中咱們提到過,Scala類有一個主構造器和任意數量的輔助構造器,而每一個輔助構造器都必須對先前定義的輔助構造器或主構造器的調用開始。

這樣作的結果是,複製構造器永遠都不可能直接調用超類的構造器。

子類的複製構造器最終都會調用主構造器。只有主構造器能夠調用超累的構造器。

主構造器是和類定義交織在一塊兒的。調用超類構造器的方式也一樣交織在一塊兒。例如:

class Person(val name: String, val age: Int){

}

class Employee(name: String, age: Int, val salary: Double) extends Person(name, age){

}

注:在父類中定義了的參數,在子類中沒必要從新定義,只要名稱相同便可,參考上面的name和age。

將類和構造器交織在一塊兒能夠給咱們帶來更精簡的代碼。把主構造器的參數當作是類參數可能更容易理解。本例中Employee類有三個參數:name、age和salary其中兩個被「傳遞」到了超類。

------------------------------------------------

Scala能夠擴展Java 類。這種狀況寫,他的主構造器必須調用java 超類的某一個構造方法:

class Square(x: Int, y:Int, width: Int) extends java.awt.Rectangle(x, y, width, width)

6.重寫字段

Scala 中重寫字段由以下限制(同時參考下表):

  • def只能重寫def。
  • val只能重寫另外一個val或不帶參數的def。
  • var只能重寫另外一個抽象的var
  用val 用def 用var
重寫val
  • 子類有一個私有字段(與超類的字段名稱相同(這沒問題))
  • getter方法重寫超類的getter方法
錯誤 錯誤
重寫def
  • 子類有一個私有字段
  • getter方法重寫超類的方法
和Java同樣 var能夠重寫getter/setter對。只重寫getter會報錯
重寫var 錯誤 錯誤 僅當超類的var是抽象的才能夠

例如:

abstract class Person{
    def id: Int 
}

class Student(override val id: Int) extends Person

7.匿名子類

和Java同樣,你能夠經過包含帶有定義或重寫的代碼塊的方式建立一個匿名的子類,好比:

val alien = new Person("Fred"){
    def greeting = "Greetings,Earthling! My name is Fred."
}

從技術上講,這將會建立出一個結構類型的對象。該類型標記爲Person{def greeting: String}。你能夠用這個做爲參數類型的定義:

def meet(p: Person{def greeting: String}){
    println(p.name + "says: " + p.greeting)
}

8.抽象類

和Java同樣,你能夠用abstract關鍵字類標記不能被實例化的類,一般由於某個或某幾個方法沒有被完整定義。例如:

abstract class Person(val name: String){
    def id: Int //沒有方法體——這是一個抽象方法
}

在子類中重寫超類的抽象方法時,你不須要使用override關鍵字。

class Employ(name: String) extends Person(name){
    def id = name.hashCode //不須要override關鍵字
}

9.抽象字段

除了抽象方法外,類還能夠擁有抽象字段。抽象字段就是一個沒有初始值的字段。例如:

abstract class Person{
    val id: Int //沒有初始化,這是一個帶有抽象的getter方法的字段
    var name: String //另外一個抽象字段,帶有抽象的getter和setter方法
}

該類爲id和name字段定義了抽象的getter方法,爲name字段定義了抽象的setter方法。生成的Java類並不帶字段。

具體的子類必須提供具體的字段,例如:

class Employee(val id:Int) extends Person{ //子類有具體的id屬性
    var name = " " //和具體的name屬性
}

和方法同樣,在子類中重寫超類中的抽象字段時,不須要override關鍵字。

你能夠鎖是用匿名類型來定製抽象字段:

val fred = new Person{
    val id - 1729
    var name = "Fred" 
}

10.構造順序和提早定義

當你在子類中重寫val而且在超類的構造器中使用該值的話,其行爲並不那麼顯而易見。有這樣一個示例:動物能夠感知其周圍的環境。簡單起見,咱們假定動物生活在一維的世界裏,而感知數據以整數表示。動物在默認狀況下能夠看到前方10個單位:

class Creature {
    val range : Int=10
    val env: Array[Int] = new Array[Int] ( range)
}

不過螞蟻是近視的:

class Ant extends Creature {
    override val range=2
}

面臨問題

咱們如今面臨一個問題:range值在超類的構造器中用到了,而超類的構造器先於子類的構造器運行。確切地說,事情發生的過程是這樣的:

  1. Ant的構造器在作它本身的構造以前,調用Creature的構造器
  2. Creature的構造器將它的range字段設爲10
  3. Creature的構造器爲了初始化env數組,調用range()取值器
  4.  該方法被重寫以輸出(還未初始化的)Ant類的range字段值
  5. range方法返回0。這是對象被分配空間時全部整型字段的初始值
  6. env被設爲長度爲0的數組
  7. Ant構造器繼續執行,將其range字段設爲2

雖然range字段看上去多是10或者2,但env被設成了長度爲0的數組。這裏的教訓是你在構造器內不該該依賴val的值。

解決方案

在Java中,當你在超類的構造方法中調用方法時,會遇到類似的問題。被調用的方法可能被子類重寫,所以它可能並不會按照你的預期行事。事實上,這就是咱們問題的核心所在range表達式調用了getter方法。有幾種解決方式:

  1. 將val聲明爲final。這樣很安全但並不靈活
  2. 在超類中將val聲明爲lazy。這樣很安全但並不高效
  3. 在子類中使用提早定義語法

提早定義語句

所謂的"提早定義"語法,讓你能夠在超類的構造器執行以前初始化子類的val字段。這個語法簡直難看到家了,估計沒人會喜歡。你須要將val字段放在位於extends關

鍵字以後的一個塊中,就像這樣:

class Ant extends {
    override val range=2
} with Creature

注意:超類的類名前的with關鍵字,這個關鍵字一般用於指定用到的特質。提早定義的等號右側只能引用以前已有的提早定義,而不能使用類中的其餘字段或方法。

11.Scala繼承層級

在Scala中,與Java基本類型相對應的類,以及Unit類型,都擴展自AnyVal。

全部其餘類都是AnyRef的子類,AnyRef至關於Java中的Object

而AnyVal與AnyRef都擴展自Any,Any類是整個繼承層級的根節點。

Any類定義了isInstanceOf、asInstanceOf方法,以及用於相等性的判斷和哈希碼的方法。

AnyVal並無追加任何方法。他只是全部值類型的標記。

AnyRef類追加了來自Object類的監事方法wait和notify/notifyAll。同時提供了一個帶函數參數的方法synchronized。這個方法等同於Java中的synchronized塊。例如:

account.synchronized{ account.balance += amount}

說明:和Java同樣,我建議你遠離wait、notify和synchronized——除非你有充分的理由使用這些關鍵字而不是更高層次的併發結構。

全部的Scala類都實現ScalaObject這個標記接口,這個藉口沒有定義任何方法。

Scala繼承層級的另外一端是Nothing和Null類型。

Null類型的惟一實例時null值,你能夠將null值賦給任何引用,但不能賦值給類型變量。

Nothing類型沒有實例,它對於泛型結構時很是有用。舉例來講,空列表Nil的類型是List[Nothing],它是List[T]的子類型,T能夠試任何類。

注:Nothing類型和Java中的void徹底是兩個概念。在Scala中void用Unit表示,該類型只有一個值,那就是()。注意Unit並非任何類型的超類型。可是依然容許任何值被替換成()。例如:

def printAny(x: Any){ println(x) }

def printUnit(x: Unit){ println(x) }

printAny("Hello") //將打印Hello

printUnit("Hello") //將「Hello」替換成(),而後調用printUnit(()),打印出()

12.對象相等性

在Scala中,AnyRef的eq方法檢查了兩個引用是否指向同一個對象。AnyRef的equals方法調用eq。當你實現類的時候應該考慮重寫equals方法,以提供一個天然地與你的實際狀況相稱的相等性判斷。

如下是相應的equals方法定義:

final override def equals(other: Any) = {
    ....
}

注意: 請確保定義的equals方法參數類型爲Any。這樣並不會重寫AnyRef的equals方法。

當你定義equals時,記得同時定義hashCode:

final override def hashCode = {  ...  }

在應用程序當中,你一般並不直接調用eq或equals,只要用==操做符就好。對於引用類型而言,他會在作完必要的null檢查後調用equals方法。

相關文章
相關標籤/搜索