1、Scala中繼承(extends)的概念
- Scala 中,讓子類繼承父類,與 Java 一樣,也是使用 extends 關鍵字;
- 繼承就代表,子類可繼承父類的 field 和 method ,然后子類還可以在自己的內部實現父類沒有的,子類特有的 field 和method,使用繼承可以有效復用代碼;
- 子類可以覆蓋父類的 field 和 method,但是如果父類用 final 修飾,或者 field 和 method 用 final 修飾,則該類是無法被繼承的,或者 field 和 method 是無法被覆蓋的。
- private 修飾的 field 和 method 不可以被子類繼承,只能在類的內部使用;
- field 必須要被定義成 val 的形式才能被繼承,並且還要使用 override 關鍵字。 因為 var 修飾的 field 是可變的,在子類中可直接引用被賦值,不需要被繼承;即 val 修飾的才允許被繼承,var 修飾的只允許被引用。繼承就是改變、覆蓋的意思。
- Java 中的訪問控制權限,同樣適用於 Scala
|
類內部 |
本包 |
子類 |
外部包 |
public |
√ |
√ |
√ |
√ |
protected |
√ |
√ |
√ |
× |
default |
√ |
√ |
× |
× |
private |
√ |
× |
× |
× |
舉例說明:
package com.starzy.extends_demo class Person { val name="super" def getName=this.name } class Student extends Person{ //繼承加上關鍵字 override val name="sub" //子類可以定義自己的field和method val score="A" def getScore=this.score } |
2、Scala中override 和 super 關鍵字
- Scala中,如果子類要覆蓋父類中的一個非抽象方法,必須要使用 override 關鍵字;子類可以覆蓋父類的 val 修飾的field,只要在子類中使用 override 關鍵字即可。
- override 關鍵字可以幫助開發者盡早的發現代碼中的錯誤,比如, override 修飾的父類方法的方法名拼寫錯誤。
-
此外,在子類覆蓋父類方法后,如果在子類中要調用父類中被覆蓋的方法,則必須要使用 super 關鍵字,顯示的指出要調用的父類方法。
舉例說明:
class Person1 { private val name = "leo" val age=50 def getName = this.name } class Student1 extends Person1{ private val score = "A" //子類可以覆蓋父類的 val field,使用override關鍵字 override val age=30 def getScore = this.score //覆蓋父類非抽象方法,必須要使用 override 關鍵字 //同時調用父類的方法,使用super關鍵字 override def getName = "your name is " + super.getName } |
3、Scala中isInstanceOf 和 asInstanceOf
如果實例化了子類的對象,但是將其賦予了父類類型的變量,在后續的過程中,又需要將父類類型的變量轉換為子類類型的變量,應該如何做?
- 首先,需要使用 isInstanceOf 判斷對象是否為指定類的對象,如果是的話,則可以使用 asInstanceOf 將對象轉換為指定類型;
- 注意: p.isInstanceOf[XX] 判斷 p 是否為 XX 對象的實例;p.asInstanceOf[XX] 把 p 轉換成 XX 對象的實例
- 注意:如果沒有用 isInstanceOf 先判斷對象是否為指定類的實例,就直接用 asInstanceOf 轉換,則可能會拋出異常;
- 注意:如果對象是 null,則 isInstanceOf 一定返回 false, asInstanceOf 一定返回 null;
-
Scala與Java類型檢查和轉換
Scala |
Java |
obj.isInstanceOf[C] |
obj instanceof C |
obj.asInstanceOf[C] |
(C)obj |
classOf[C] |
C.class |
舉例說明:
class Person3 {} class Student3 extends Person3 object Student3{ def main (args: Array[String] ) { val p: Person3 = new Student3 var s: Student3 = null //如果對象是 null,則 isInstanceOf 一定返回 false println (s.isInstanceOf[Student3]) // 判斷 p 是否為 Student3 對象的實例 if (p.isInstanceOf[Student3] ) { //把 p 轉換成 Student3 對象的實例 s = p.asInstanceOf[Student3] } println (s.isInstanceOf[Student3] ) }
} |
4、Scala中getClass 和 classOf
- isInstanceOf 只能判斷出對象是否為指定類以及其子類的對象,而不能精確的判斷出,對象就是指定類的對象;
- 如果要求精確地判斷出對象就是指定類的對象,那么就只能使用 getClass 和 classOf 了;
-
p.getClass 可以精確地獲取對象的類,classOf[XX] 可以精確的獲取類,然后使用 == 操作符即可判斷;
舉例說明:
class Person4 {} class Student4 extends Person4 object Student4{ def main(args: Array[String]) { val p:Person4=new Student4 //判斷p是否為Person4類的實例 println(p.isInstanceOf[Person4])//true //判斷p的類型是否為Person4類 println(p.getClass == classOf[Person4])//false //判斷p的類型是否為Student4類 println(p.getClass == classOf[Student4])//true } } |
5、Scala中使用模式匹配進行類型判斷
- 在實際的開發中,比如 spark 源碼中,大量的地方使用了模式匹配的語法進行類型的判斷,這種方式更加地簡潔明了,而且代碼的可維護性和可擴展性也非常高;
- 使用模式匹配,功能性上來說,與 isInstanceOf 的作用一樣,主要判斷是否為該類或其子類的對象即可,不是精准判斷。
-
等同於 Java 中的 switch case 語法;
舉例說明:
class Person5 {} class Student5 extends Person5 object Student5{ def main(args: Array[String]) { val p:Person5=new Student5 p match { // 匹配是否為Person類或其子類對象 case per:Person5 => println("This is a Person5's Object!") // 匹配所有剩余情況 case _ =>println("Unknown type!") } }
} |
6、Scala中protected
- 跟 Java 一樣,Scala 中同樣可使用 protected 關鍵字來修飾 field 和 method。在子類中,可直接訪問父類的 field 和 method,而不需要使用 super 關鍵字;
- 還可以使用 protected[this] 關鍵字, 訪問權限的保護范圍:只允許在當前子類中訪問父類的 field 和 method,不允許通過其他子類對象訪問父類的 field 和 method。
舉例說明:
class Person6{ protected var name:String="tom" protected[this] var hobby:String ="game" protected def sayBye=println("再見...") } class Student6 extends Person6{ //父類使用protected 關鍵字來修飾 field可以直接訪問 def sayHello =println("Hello "+name) //父類使用protected 關鍵字來修飾method可以直接訪問 def sayByeBye=sayBye def makeFriends(s:Student6)={ println("My hobby is "+hobby+", your hobby is UnKnown") } } object Student6{ def main(args: Array[String]) { val s:Student6=new Student6 s.sayHello s.makeFriends(s) s.sayByeBye } } |
7、Scala中調用父類的constructor
- Scala中,每個類都可以有一個主constructor和任意多個輔助constructor,而且每個輔助constructor的第一行都必須調用其他輔助constructor或者主constructor代碼;因此子類的輔助constructor是一定不可能直接調用父類的constructor的;
- 只能在子類的主constructor中調用父類的constructor。
-
如果父類的構造函數已經定義過的 field,比如name和age,子類再使用時,就不要用 val 或 var 來修飾了,否則會被認為,子類要覆蓋父類的field,且要求一定要使用 override 關鍵字。
舉例說明:
class Person7(val name:String,val age:Int){ var score :Double=0.0 var address:String="beijing" def this(name:String,score:Double)={ //每個輔助constructor的第一行都必須調用其他輔助constructor或者主constructor代碼 //主constructor代碼 this(name,30) this.score=score } //其他輔助constructor def this(name:String,address:String)={ this(name,100.0) this.address=address } } class Student7(name:String,score:Double) extends Person7(name,score) |
8、Scala中抽象類
- 如果在父類中,有某些方法無法立即實現,而需要依賴不同的子類來覆蓋,重寫實現不同的方法。此時,可以將父類中的這些方法編寫成只含有方法簽名,不含方法體的形式,這種形式就叫做抽象方法;
- 一個類中,如果含有一個抽象方法或抽象field,就必須使用abstract將類聲明為抽象類,該類是不可以被實例化的;
-
在子類中覆蓋抽象類的抽象方法時,可以不加override關鍵字;
舉例說明:
abstract class Person9(val name:String) { //必須指出返回類型,不然默認返回為Unit def sayHello:String def sayBye:String } class Student9(name:String) extends Person9(name){ //必須指出返回類型,不然默認 def sayHello: String = "Hello,"+name def sayBye: String ="Bye,"+name } object Student9{ def main(args: Array[String]) { val s = new Student9("tom") println(s.sayHello) println(s.sayBye) } } |
9、Scala中抽象field
-
如果在父類中,定義了field,但是沒有給出初始值,則此field為抽象field;
舉例說明:
abstract class Person10 (val name:String){ //抽象fields val age:Int } class Student10(name: String) extends Person10(name) { val age: Int = 50 } |