Scala 中繼承關係以下圖:java
Scala 的集成機制和 Java 有不少類似之處,好比都使用 extends
關鍵字表示繼承,都使用 override
關鍵字表示重寫父類的方法或成員變量。示例以下:git
//父類 class Person { var name = "" // 1.不加任何修飾詞,默認爲 public,能被子類和外部訪問 var age = 0 // 2.使用 protected 修飾的變量能子類訪問,可是不能被外部訪問 protected var birthday = "" // 3.使用 private 修飾的變量不能被子類和外部訪問 private var sex = "" def setSex(sex: String): Unit = { this.sex = sex } // 4.重寫父類的方法建議使用 override 關鍵字修飾 override def toString: String = name + ":" + age + ":" + birthday + ":" + sex }
使用 extends
關鍵字實現繼承:github
// 1.使用 extends 關鍵字實現繼承 class Employee extends Person { override def toString: String = "Employee~" + super.toString // 2.使用 public 或 protected 關鍵字修飾的變量能被子類訪問 def setBirthday(date: String): Unit = { birthday = date } }
測試繼承:編程
object ScalaApp extends App { val employee = new Employee employee.name = "heibaiying" employee.age = 20 employee.setBirthday("2019-03-05") employee.setSex("男") println(employee) } // 輸出: Employee~heibaiying:20:2019-03-05:男
在 Scala 的類中,每一個輔助構造器都必須首先調用其餘構造器或主構造器,這樣就致使了子類的輔助構造器永遠沒法直接調用超類的構造器,只有主構造器才能調用超類的構造器。因此想要調用超類的構造器,代碼示例以下:ide
class Employee(name:String,age:Int,salary:Double) extends Person(name:String,age:Int) { ..... }
想要實現類檢查可使用 isInstanceOf
,判斷一個實例是否來源於某個類或者其子類,若是是,則可使用 asInstanceOf
進行強制類型轉換。測試
object ScalaApp extends App { val employee = new Employee val person = new Person // 1. 判斷一個實例是否來源於某個類或者其子類 輸出 true println(employee.isInstanceOf[Person]) println(person.isInstanceOf[Person]) // 2. 強制類型轉換 var p: Person = employee.asInstanceOf[Person] // 3. 判斷一個實例是否來源於某個類 (而不是其子類) println(employee.getClass == classOf[Employee]) }
在 Scala 中還有一個須要注意的問題,若是你在子類中重寫父類的 val 變量,而且超類的構造器中使用了該變量,那麼可能會產生不可預期的錯誤。下面給出一個示例:大數據
// 父類 class Person { println("父類的默認構造器") val range: Int = 10 val array: Array[Int] = new Array[Int](range) } //子類 class Employee extends Person { println("子類的默認構造器") override val range = 2 } //測試 object ScalaApp extends App { val employee = new Employee println(employee.array.mkString("(", ",", ")")) }
這裏初始化 array 用到了變量 range,這裏你會發現實際上 array 既不會被初始化 Array(10),也不會被初始化爲 Array(2),實際的輸出應該以下:this
父類的默認構造器 子類的默認構造器 ()
能夠看到 array 被初始化爲 Array(0),主要緣由在於父類構造器的執行順序先於子類構造器,這裏給出實際的執行步驟:scala
new Array[Int](range)
語句;override val
重寫變量的同時也重寫了其 get 方法;range = 2
語句尚未被執行,因此天然返回 range 的默認值,也就是 0。這裏可能比較疑惑的是爲何 val range = 2
沒有被執行,卻能使用 range 變量,這裏由於在虛擬機層面,是先對成員變量先分配存儲空間並賦給默認值,以後才賦予給定的值。想要證實這一點其實也比較簡單,代碼以下:設計
class Person { // val range: Int = 10 正常代碼 array 爲 Array(10) val array: Array[Int] = new Array[Int](range) val range: Int = 10 //若是把變量的聲明放在使用以後,此時數據 array 爲 array(0) } object Person { def main(args: Array[String]): Unit = { val person = new Person println(person.array.mkString("(", ",", ")")) } }
想要解決上面的問題,有如下幾種方法:
(1) . 將變量用 final 修飾,表明不容許被子類重寫,即 final val range: Int = 10
;
(2) . 將變量使用 lazy 修飾,表明懶加載,即只有當你實際使用到 array 時候,纔去進行初始化;
lazy val array: Array[Int] = new Array[Int](range)
(3) . 採用提早定義,代碼以下,表明 range 的定義優先於超類構造器。
class Employee extends { //這裏不能定義其餘方法 override val range = 2 } with Person { // 定義其餘變量或者方法 def pr(): Unit = {println("Employee")} }
可是這種語法也有其限制:你只能在上面代碼塊中重寫已有的變量,而不能定義新的變量和方法,定義新的變量和方法只能寫在下面代碼塊中。
注意事項:類的繼承和下文特質 (trait) 的繼承都存在這個問題,也一樣能夠經過提早定義來解決。雖然如此,但仍是建議合理設計以規避該類問題。
Scala 中容許使用 abstract
定義抽象類,而且經過 extends
關鍵字繼承它。
定義抽象類:
abstract class Person { // 1.定義字段 var name: String val age: Int // 2.定義抽象方法 def geDetail: String // 3. scala 的抽象類容許定義具體方法 def print(): Unit = { println("抽象類中的默認方法") } }
繼承抽象類:
class Employee extends Person { // 覆蓋抽象類中變量 override var name: String = "employee" override val age: Int = 12 // 覆蓋抽象方法 def geDetail: String = name + ":" + age }
Scala 中沒有 interface 這個關鍵字,想要實現相似的功能,可使用特質 (trait)。trait 等價於 Java 8 中的接口,由於 trait 中既能定義抽象方法,也能定義具體方法,這和 Java 8 中的接口是相似的。
// 1.特質使用 trait 關鍵字修飾 trait Logger { // 2.定義抽象方法 def log(msg: String) // 3.定義具體方法 def logInfo(msg: String): Unit = { println("INFO:" + msg) } }
想要使用特質,須要使用 extends
關鍵字,而不是 implements
關鍵字,若是想要添加多個特質,可使用 with
關鍵字。
// 1.使用 extends 關鍵字,而不是 implements,若是想要添加多個特質,可使用 with 關鍵字 class ConsoleLogger extends Logger with Serializable with Cloneable { // 2. 實現特質中的抽象方法 def log(msg: String): Unit = { println("CONSOLE:" + msg) } }
和方法同樣,特質中的字段能夠是抽象的,也能夠是具體的:
trait Logger { // 抽象字段 var LogLevel:String // 具體字段 var LogType = "FILE" }
覆蓋抽象字段:
class InfoLogger extends Logger { // 覆蓋抽象字段 override var LogLevel: String = "INFO" }
Scala 支持在類定義的時混入 父類 trait
,而在類實例化爲具體對象的時候指明其實際使用的 子類 trait
。示例以下:
trait Logger:
// 父類 trait Logger { // 定義空方法 日誌打印 def log(msg: String) {} }
trait ErrorLogger:
// 錯誤日誌打印,繼承自 Logger trait ErrorLogger extends Logger { // 覆蓋空方法 override def log(msg: String): Unit = { println("Error:" + msg) } }
trait InfoLogger:
// 通知日誌打印,繼承自 Logger trait InfoLogger extends Logger { // 覆蓋空方法 override def log(msg: String): Unit = { println("INFO:" + msg) } }
具體的使用類:
// 混入 trait Logger class Person extends Logger { // 調用定義的抽象方法 def printDetail(detail: String): Unit = { log(detail) } }
這裏經過 main 方法來測試:
object ScalaApp extends App { // 使用 with 指明須要具體使用的 trait val person01 = new Person with InfoLogger val person02 = new Person with ErrorLogger val person03 = new Person with InfoLogger with ErrorLogger person01.log("scala") //輸出 INFO:scala person02.log("scala") //輸出 Error:scala person03.log("scala") //輸出 Error:scala }
這裏前面兩個輸出比較明顯,由於只指明瞭一個具體的 trait
,這裏須要說明的是第三個輸出,由於 trait 的調用是由右到左開始生效的,因此這裏打印出 Error:scala
。
trait
有默認的無參構造器,可是不支持有參構造器。一個類混入多個特質後初始化順序應該以下:
// 示例 class Employee extends Person with InfoLogger with ErrorLogger {...}
更多大數據系列文章能夠參見 GitHub 開源項目: 大數據入門指南