繼承與特質(zhì)
繼承
繼承的概念
使用extends關(guān)鍵字:
package hhb.cn.part05
class person(name: String, age: Int)
//默認(rèn)的類構(gòu)造器屬性是val,只有g(shù)etter方法,stuNo使用var修飾,所以有g(shù)etter setter方法
class Student(name: String, age: Int, var stuNo: String) extends person(name, age)
object ExtendsDemo {
def main(args: Array[String]): Unit = {
val student = new Student("張三", 20, "1001")
student.stuNo = "1002"
println(student.stuNo)
}
}
構(gòu)造器的執(zhí)行順序
Scala在繼承的時(shí)候構(gòu)造器的執(zhí)行順序:首先執(zhí)行父類的主構(gòu)造器,其次執(zhí)行子類自身的主構(gòu)造器。
類有一個(gè)主構(gòu)造器和任意數(shù)量的輔助構(gòu)造器,而每個(gè)輔助構(gòu)造器都必須以對(duì)先前定義的輔助構(gòu)造器或主構(gòu)造器的調(diào)用開始。
子類的輔助構(gòu)造器最終都會(huì)調(diào)用主構(gòu)造器。只有主構(gòu)造器可以調(diào)用父類的構(gòu)造器。
package hhb.cn.part05
class person(name: String, age: Int) {
println("這是父類構(gòu)造器!")
}
//默認(rèn)的類構(gòu)造器屬性是val,只有g(shù)etter方法,stuNo使用var修飾,所以有g(shù)etter setter方法
class Student(name: String, age: Int, var stuNo: String) extends person(name, age) {
println("這是子類構(gòu)造器")
}
object ExtendsDemo {
def main(args: Array[String]): Unit = {
val student = new Student("張三", 20, "1001")
student.stuNo = "1002"
println(student.stuNo)
}
}
輸出:
這是父類構(gòu)造器!
這是子類構(gòu)造器
1002
override方法重寫
Scala在繼承的時(shí)候構(gòu)造器的執(zhí)行順序:首先執(zhí)行父類的主構(gòu)造器,其次執(zhí)行子類自身的主構(gòu)造器。
類有一個(gè)主構(gòu)造器和任意數(shù)量的輔助構(gòu)造器,而每個(gè)輔助構(gòu)造器都必須以對(duì)先前定義的輔助構(gòu)造器或主構(gòu)造器的調(diào)用開始。
子類的輔助構(gòu)造器最終都會(huì)調(diào)用主構(gòu)造器。只有主構(gòu)造器可以調(diào)用父類的構(gòu)造器。
package hhb.cn.part05
class Programmer(name: String, age: Int) {
def coding: Unit = {
println("我在寫代碼")
}
}
class ScalaProgrammer(name: String, age: Int, workNo: String) extends Programmer(name, age) {
override def coding: Unit = {
//調(diào)用父類的方法
super.coding
//增加自己的實(shí)現(xiàn)
println("我在用Scala寫代碼")
}
}
object OverrideDemo {
def main(args: Array[String]): Unit = {
val scalaProgrammer = new ScalaProgrammer("張三", 20, "1001")
scalaProgrammer.coding
}
}
需要強(qiáng)調(diào)一點(diǎn):如果父類是抽象類,則override關(guān)鍵字可以不加。如果繼承的父類是抽象類(假設(shè)抽象類為AbstractClass,子類為SubClass),在SubClass類中,AbstractClass對(duì)應(yīng)的抽象方法如果沒有實(shí)現(xiàn)的話,那SubClass也必須定義為抽象類,否則的話必須要有方法的實(shí)現(xiàn)。
package hhb.cn.part05
abstract class Person(name: String, age: Int) {
def talk: Unit
}
class Teacher(name: String, age: Int, workNo: String) extends Person(name, age) {
//重寫抽象類的walk方法,可以不加override方法
def talk: Unit = {
println("重寫父類的方法")
}
}
object AbstractDemo {
def main(args: Array[String]): Unit = {
val teacher = new Teacher("張三", 20, "123")
teacher.talk
}
}
類型檢查與轉(zhuǎn)換
要測試某個(gè)對(duì)象是否屬于某個(gè)給定的類,可以用isInstanceOf方法。如果測試成功,可以用asInstanceOf方法進(jìn)行類型轉(zhuǎn)換。
if(p.isInstanceOf[Employee]){
//s的類型轉(zhuǎn)換為Employee
val s = p.asInstanceOf[Employee]
}
如果p指向的是Employee類及其子類的對(duì)象,則p.isInstanceOf[Employee]將會(huì)成功。
如果p是null,則p.isInstanceOf[Employee]將返回false,且p.asInstanceOf[Employee]將返回null。
如果p不是一個(gè)Employee,則p.asInstanceOf[Employee]將拋出異常。
如果想要測試p指向的是一個(gè)Employee對(duì)象但又不是其子類,可以用:
if(p.getClass == classOf[Employee])
classOf方法定義在scala.Preder對(duì)象中,因此會(huì)被自動(dòng)引入。
不過,與類型檢查和轉(zhuǎn)換相比,模式匹配通常是更好的選擇。
p match{
//將s作為Employee處理
case s: Employee => ...
//p不是Employee的情況
case _ => ....
}
示例:
package hhb.cn.part05
class Person2
class Student2 extends Person2
object InstanceDemo {
def main(args: Array[String]): Unit = {
val p: Person2 = new Student2
var s: Student2 = null
//如果對(duì)象s 是一個(gè)null,isInstanceOf返回是false
println(s.isInstanceOf[Student2])
// 判斷對(duì)象p是不是Student2這個(gè)類型
if (p.isInstanceOf[Student2]) { // true
//把p這個(gè)對(duì)象轉(zhuǎn)化為Student2類型后賦值給s
s = p.asInstanceOf[Student2]
}
// 輸出s是否是Student2
println(s.isInstanceOf[Student2]) // true
//此時(shí)p已經(jīng)是Student2類型
println(p.getClass == classOf[Person2]) //false
println(p.getClass == classOf[Student2]) // true
println("-================================")
p match {
case s: Student2 => println("它是Student2類型的對(duì)象")
case _ => println("它什么也不是")
}
}
}
特質(zhì)
作為接口使用的特質(zhì)
Scala中的trait特質(zhì)是一種特殊的概念。
首先可以將trait作為接口來使用,此時(shí)的trait就與Java中的接口非常類似。
在trait中可以定義抽象方法,與抽象類中的抽象方法一樣,只要不給出方法的具體實(shí)現(xiàn)即可。
類可以使用extends關(guān)鍵字繼承trait。
注意:在Scala中沒有implement的概念,無論繼承類還是trait特質(zhì),統(tǒng)一都是extends。
類繼承trait特質(zhì)后,必須實(shí)現(xiàn)其中的抽象方法,實(shí)現(xiàn)時(shí)可以省略override關(guān)鍵字。
Scala不支持對(duì)類進(jìn)行多繼承,但是支持多重繼承trait特質(zhì),使用with關(guān)鍵字即可。
package hhb.cn.part06
trait HelloTrait {
def seyHello: String
}
trait MakeFriendTrait {
def makerFriend
}
class Person(name: String) extends HelloTrait with MakeFriendTrait {
override def seyHello: String = {
println(s"Hello,My Name is $name")
name
}
override def makerFriend: Unit = {
println(s"Hi, $name")
}
}
//如果一個(gè)類繼承了多個(gè)trait,第一個(gè)Trait用extends,其他的Trait用with關(guān)鍵字
object TraitDemo extends App {
val person = new Person("zhangsan")
println(person.seyHello)
person.makerFriend
}
帶有具體實(shí)現(xiàn)的特質(zhì)
具體方法
Scala中的trait特質(zhì)不僅僅可以定義抽象方法,還可以定義具體實(shí)現(xiàn)的方法,這時(shí)的trait更像是包含了通用工具方法的類。比如,trait中可以包含一些很多類都通用的功能方法,比如打印日志等等,Spark中就使用了trait來定義通用的日志打印方法。
具體字段
Scala trait特質(zhì)中的字段可以是抽象的,也可以是具體的。
package hhb.cn.part06
trait People {
val name: String
val age = 30
val score: Double = 100
def eat: Unit = {
println("Eating........")
}
def test
}
trait Worker {
val age = 20
def work: Unit = {
println("Working.........")
}
}
class Student extends People with Worker {
//重寫抽象字段name,此處override可以省略
override val name: String = "lisi"
//由于Worker和People中都有age字段,所以當(dāng)繼承這兩個(gè)類需要重寫age字段,此時(shí)override關(guān)鍵字不能省略,否則會(huì)報(bào)錯(cuò)
override val age: Int = 25
override def test: Unit = {
println("============")
}
}
object TraitDemoTwo extends App {
val student = new Student
student.eat
student.work
println(student.name)
println(student.age)
println(student.score)
student.test
}
注意:特質(zhì)Person和Worker中都有age字段,當(dāng)Student繼承這兩個(gè)特質(zhì)時(shí),需要重寫age字段,并且要用override關(guān)鍵字,否則就會(huì)報(bào)錯(cuò)。
特質(zhì)構(gòu)造順序
在Scala中,trait特質(zhì)也是有構(gòu)造器的,也就是trait中的不包含在任何方法中的代碼。
構(gòu)造器以如下順序執(zhí)行:
執(zhí)行父類的構(gòu)造器;
執(zhí)行trait的構(gòu)造器,多個(gè)trait從左到右依次執(zhí)行;
構(gòu)造trait時(shí)會(huì)先構(gòu)造父trait,如果多個(gè)trait繼承同一個(gè)父trait,則父trait只會(huì)構(gòu)造一次;
所有trait構(gòu)造完畢之后,子類的構(gòu)造器才執(zhí)行
package hhb.cn.part06
class Person2 {
println("Person's Constructor!")
}
trait Logger {
println("Logger's Constructor!")
}
trait MyLogger extends Logger {
println("MyLogger's Constructor!")
}
trait TimeLogger extends Logger {
println("TimeLogger's Constructor!")
}
//如果一個(gè)類繼承了父類,也繼承了特質(zhì),要先寫父類,在寫特質(zhì)
// extends 父類 with 特質(zhì)
class Student2 extends Person2 with MyLogger with Logger with TimeLogger {
println("Student2======")
}
object TraitDemoThree extends App {
val student = new Student2
}
執(zhí)行結(jié)果
Person's Constructor!
Logger's Constructor!
MyLogger's Constructor!
TimeLogger's Constructor!
Student2======
特質(zhì)繼承類
在Scala中,trait特質(zhì)也可以繼承class類,此時(shí)這個(gè)class類就會(huì)成為所有繼承此trait的類的父類。
package hhb.cn.part06
class MyUtil {
def printMessage(name: String): Unit = {
println("Hello, " + name)
}
}
trait Log extends MyUtil {
def sayHi(message: String): Unit = {
println("Hi, " + message)
}
}
// MyStudent 繼承了Log特質(zhì),Log特質(zhì)繼承了MyUtil類,那么MyUtil就成了MyStudent 的父類,繼承的傳遞性
class MyStudent(name: String) extends Log {
def log(): Unit = {
println("MyStudent:" + name)
printMessage(name)
sayHi(name)
}
}
object TraitDemoFour {
def main(args: Array[String]): Unit = {
val student = new MyStudent("zhangsan")
student.log()
}
}
使用 trait 實(shí)現(xiàn)適配器模式
當(dāng)某個(gè)特質(zhì)中有多個(gè)抽象方法,而我們只需要用到某個(gè)或某幾個(gè)方法時(shí)不得不將該特質(zhì)所有抽象方法重寫。針對(duì)這種情況可以定義一個(gè)抽象類繼承該特質(zhì),重寫特質(zhì)的所有方法,方法體為空。需要使用哪個(gè)方法只需要定義類繼承抽象類,重寫指定方法即可。
package hhb.bilibili.oop
/**
* @description:
* @date: 2020-10-19 21:38
**/
object ClassDemo20 {
trait LOL {
def top()
def mid()
def adc()
def support()
}
abstract class Player extends LOL {
override def top(): Unit = {
}
override def mid(): Unit = {
}
override def adc(): Unit = {
}
override def support(): Unit = {
}
}
class GreenHand extends Player {
override def support(): Unit = {
println("輔助")
}
}
//Player 就是適配器類
def main(args: Array[String]): Unit = {
val hand = new GreenHand
hand.support()
}
}
使用 trait 實(shí)現(xiàn)模板方法模式
在 Scala 中我們可以先定義一個(gè)操作中的算法骨架,而將算法的一些步驟延遲到子類中,使得子類可以不改變該結(jié)構(gòu)的情況下重定義該算法的某些特定步驟,這就是模板方法設(shè)計(jì)模式。
優(yōu)點(diǎn):擴(kuò)展性強(qiáng)、符號(hào)開閉原則。
缺點(diǎn):類的個(gè)數(shù)增加會(huì)導(dǎo)致系統(tǒng)龐大,設(shè)計(jì)更抽象、增加代碼閱讀難度。
package hhb.bilibili.oop
/**
* @description:
* @date: 2020-10-19 21:45
**/
object ClassDemo21 {
abstract class Template {
def code()
def getRunTime(): Unit = {
val l1 = System.currentTimeMillis()
code()
val l2 = System.currentTimeMillis()
println(l2 - l1)
}
}
class ForDemo extends Template {
override def code(): Unit = for (i <- 1 to 10000) println("hello Scala")
}
def main(args: Array[String]): Unit = {
val demo = new ForDemo
demo.getRunTime()
}
}
使用 trait 實(shí)現(xiàn)責(zé)任鏈模式
多個(gè) trait 中出現(xiàn)了同一方法,且該方法最后都調(diào)用了 super.該方法名(),當(dāng)類繼承了這多個(gè) trait 后就可以依次調(diào)用多個(gè) trait 中的此同一個(gè)方法了,這就形成了一個(gè)調(diào)用鏈。
執(zhí)行順序:從右至左、先子類后父類。
package hhb.bilibili.oop
/**
* @description:
* @author: huanghongbo
* @date: 2020-10-19 22:03
**/
object ClassDemo22 {
trait Handle {
def handler(data: String): Unit = {
println("具體要處理的數(shù)據(jù)。。。4")
println(data) // 5
}
}
trait DataValidHandle extends Handle {
override def handler(data: String): Unit = {
println("驗(yàn)證數(shù)據(jù) 3")
//核心:調(diào)用父特質(zhì)的handle方法
super.handler(data)
}
}
trait SignHandle extends Handle {
override def handler(data: String): Unit = {
println("驗(yàn)簽數(shù)據(jù) 2")
//核心:調(diào)用父特質(zhì)的handle方法
super.handler(data)
}
}
// 疊加特質(zhì)
class Payment extends DataValidHandle with SignHandle {
override def handler(data: String): Unit = {
println("用戶發(fā)起支付請求 1")
super.handler(data)
}
}
/**
* 執(zhí)行順序:
* 1. 。按照從右向左的順序,從右至左、先子類后父類。
* @param args
*/
def main(args: Array[String]): Unit = {
val payment = new Payment
payment.handler("123")
}
}
Ordered和Ordering
在Java中對(duì)象的比較有兩個(gè)接口,分別是Comparable和Comparator。它們之間的區(qū)別在于:
實(shí)現(xiàn)Comparable接口的類,重寫compareTo()方法后,其對(duì)象自身就具有了可比較性;
實(shí)現(xiàn)Comparator接口的類,重寫了compare()方法后,則提供一個(gè)第三方比較器,用于比較兩個(gè)對(duì)象。
在Scala中也引入了以上兩種比較方法(Scala.math包下):
Ordered特質(zhì)混入Java的Comparable接口,它定義了相同類型間的比較方式,但這種內(nèi)部比較方式是單一的;
trait Ordered[A] extends Any with java.lang.Comparable[A]{......}
Ordering特質(zhì)混入Comparator接口,它是提供第三方比較器,可以自定義多種比較方式,在實(shí)際開發(fā)中也是使用比較多的,靈活解耦合。
trait Ordering[T] extends Comparator[T] with PartialOrdering[T] with Serializable {......}
排序操作
package hhb.cn.part06
import scala.util.Sorting
case class Project(tag: String, score: Int) extends Ordered[Project] {
override def compare(that: Project): Int = {
tag.compareTo(that.tag)
}
}
object OrderDemo {
def main(args: Array[String]): Unit = {
//字典序
val list = List(Project("hadoop", 40), Project("flink", 90), Project("hive", 30), Project("kafka", 20))
println(list.sorted)
val tuples = Array(("a", 3, 'b'), ("b", 1, 'c'), ("c", 2, 'a'))
//默認(rèn)使用第一個(gè)字母排序
Sorting.quickSort(tuples)
println(tuples.toBuffer)
//Ordering.by[(String, Int, Char), Int](_._2) 將一個(gè)(String, Int, Char)
// 類型的Tuple元組轉(zhuǎn)換成Int類型,并根據(jù)Tuple元組中第二個(gè)元素排序
Sorting.quickSort(tuples)(Ordering.by[(String, Int, Char), Int](_._2))
println(tuples.toBuffer)
}
}
