目錄
三十二、繼承
scala語言是支持面向對象編程的, 可以使用scala來實現繼承,通過繼承來減少重複代碼。
定義語法
- 使用extends關鍵字來實現繼承
- 可以在子類中定義父類中沒有的字段和方法,或者重寫父類的方法
- 類和單例對象都可以從某個父類繼承
class/object 子類 extends 父類 {
..
}
示例 | 類繼承
- 定義一個Person類,再定義一個Student類,繼承自Person類
- 創建一個Student類對象實例,並設置name爲“張三”
- 打印姓名
class Person {
var name = "super"
def getName = this.name
}
class Student extends Person
object Main13 {
def main(args: Array[String]): Unit = {
val p1 = new Person()
val p2 = new Student()
p2.name = "張三"
println(p2.getName)
}
}
示例 | 單例對象繼承
- 創建一個Student單例對象,讓單例對象繼承示例1中的Person類
- 設置單例對象的名字爲"張三",調用Student單例對象的getName方法
class Person {
var name = "super"
def getName = this.name
}
object Student extends Person
object Main13 {
def main(args: Array[String]): Unit = {
println(Student.getName)
}
}
三十三、override和super
類似於Java語言, 在子類中使用override需要來重寫父類的成員,可以使用super來引用父類
- 子類要覆蓋父類中的一個方法,必須要使用override關鍵字
- 使用override來重寫一個val字段
- 使用super關鍵字來訪問父類的成員方法
示例
- 定義一個Person類,包含
- 姓名字段(不可重新賦值)
- 獲取姓名方法
- 定義一個Student類
- 重寫姓名字段
- 重寫獲取姓名方法,返回"hello, " + 姓名
- 創建Student對象示例,調用它的getName方法
class Person {
val name = "super"
def getName = name
}
class Student extends Person {
// 重寫val字段
override val name: String = "child"
// 重寫getName方法
override def getName: String = "hello, " + super.getName
}
object Main13 {
def main(args: Array[String]): Unit = {
println(new Student().getName)
}
}
三十四、類型判斷
有時候,我們設計的程序,要根據變量的類型來執行對應的邏輯。
在scala中有兩種方式進行類型判斷
- isInstanceOf
- getClass/classOf
isInstanceOf/asInstanceOf
在Java中,可以使用instanceof關鍵字來判斷類型、以及(類型)object來進行類型轉換,在scala中如何實現?
scala中對象提供isInstanceOf和asInstanceOf方法。
- isInstanceOf判斷對象是否爲指定類的對象
- asInstanceOf將對象轉換爲指定類型
用法
// 判斷對象是否爲指定類型
val trueOrFalse:Boolean = 對象.isInstanceOf[類型]
// 將對象轉換爲指定類型
val 變量 = 對象.asInstanceOf[類型]
示例
- 定義一個Person類
- 定義一個Student類繼承自Person類
- 創建一個Student類對象
- 判斷該對象是否爲Student類型,如果是,將其轉換爲Student類型並打印該對象
class Person3
class Student3 extends Person3
object Main3 {
def main(args: Array[String]): Unit = {
val s1:Person3 = new Student3
// 判斷s1是否爲Student3類型
if(s1.isInstanceOf[Student3]) {
// 將s1轉換爲Student3類型
val s2 = s1.asInstanceOf[Student3]
println(s2)
}
}
}
getClass和classOf
isInstanceOf 只能判斷對象是否爲指定類以及其子類的對象,而不能精確的判斷出,對象就是指定類的對象。如果要求精確地判斷出對象就是指定類的對象,那麼就只能使用 getClass 和 classOf 。
用法
- p.getClass可以精確獲取對象的類型
- classOf[x]可以精確獲取類型
- 使用==操作符可以直接比較類型
示例
- 定義一個Person類
- 定義一個Student類繼承自Person類
- 創建一個Student類對象,並指定它的類型爲Person類型
- 測試使用isInstance判斷該對象是否爲Person類型
- 測試使用getClass/classOf判斷該對象是否爲Person類型
- 測試使用getClass/classOf判斷該對象是否爲Student類型
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
}
}
三十五、抽象類
和Java語言一樣,scala中也可以定義抽象類
定義
如果類的某個成員在當前類中的定義是不包含完整的,它就是一個抽象類
不完整定義有兩種情況:
- 方法沒有方法體(抽象方法)
- 變量沒有初始化(抽象字段)
定義抽象類和Java一樣,在類前面加上abstract關鍵字
// 定義抽象類
abstract class 抽象類名 {
// 定義抽象字段
val 抽象字段名:類型
// 定義抽象方法
def 方法名(參數:參數類型,參數:參數類型...):返回類型
}
抽象方法
- 設計4個類,表示上述圖中的繼承關係
- 每一個形狀都有自己求面積的方法,但是不同的形狀計算面積的方法不同
步驟
- 創建一個Shape抽象類,添加一個area抽象方法,用於計算面積
- 創建一個Square正方形類,繼承自Shape,它有一個邊長的主構造器,並實現計算面積方法
- 創建一個長方形類,繼承自Shape,它有一個長、寬的主構造器,實現計算面積方法
- 創建一個圓形類,繼承自Shape,它有一個半徑的主構造器,並實現計算面積方法
- 編寫main方法,分別創建正方形、長方形、圓形對象,並打印它們的面積
// 創建形狀抽象類
abstract class Shape {
def area:Double
}
// 創建正方形類
class Square(var edge:Double /*邊長*/) extends Shape {
// 實現父類計算面積的方法
override def area: Double = edge * edge
}
// 創建長方形類
class Rectangle(var length:Double /*長*/, var width:Double /*寬*/) extends Shape {
override def area: Double = length * width
}
// 創建圓形類
class Cirle(var radius:Double /*半徑*/) extends Shape {
override def area: Double = Math.PI * radius * radius
}
object Main6 {
def main(args: Array[String]): Unit = {
val s1:Shape = new Square(2)
val s2:Shape = new Rectangle(2,3)
val s3:Shape = new Cirle(2)
println(s1.area)
println(s2.area)
println(s3.area)
}
}
三十六、抽象字段
在scala中,也可以定義抽象的字段。如果一個成員變量是沒有初始化,我們就認爲它是抽象的。
abstract class 抽象類 {
val/var 抽象字段:類型
}
- 創建一個Person抽象類,它有一個String抽象字段WHO_AM_I
- 創建一個Student類,繼承自Person類,重寫WHO_AM_I字段,初始化爲學生
- 創建一個Policeman類,繼承自Person類,重寫WHO_AM_I字段,初始化警察
- 添加main方法,分別創建Student/Policeman的實例,然後分別打印WHO_AM_I
// 定義一個人的抽象類
abstract class Person6 {
// 沒有初始化的val字段就是抽象字段
val WHO_AM_I:String
}
class Student6 extends Person6 {
override val WHO_AM_I: String = "學生"
}
class Policeman6 extends Person6 {
override val WHO_AM_I: String = "警察"
}
object Main6 {
def main(args: Array[String]): Unit = {
val p1 = new Student6
val p2 = new Policeman6
println(p1.WHO_AM_I)
println(p2.WHO_AM_I)
}
}
三十七、匿名內部類
匿名內部類是沒有名稱的子類,直接用來創建實例對象。Spark的源代碼中有大量使用到匿名內部類。
scala中的匿名內部類使用與Java一致。
val/var 變量名 = new 類/抽象類 {
// 重寫方法
}
- 創建一個Person抽象類,並添加一個sayHello抽象方法
- 添加main方法,通過創建匿名內部類的方式來實現Person
- 調用匿名內部類對象的sayHello方法
abstract class Person7 {
def sayHello:Unit
}
object Main7 {
def main(args: Array[String]): Unit = {
// 直接用new來創建一個匿名內部類對象
val p1 = new Person7 {
override def sayHello: Unit = println("我是一個匿名內部類")
}
p1.sayHello
}
}
三十八、特質(trait)
scala中沒有Java中的接口(interface),替代的概念是——特質
- 特質是scala中代碼複用的基礎單元
- 它可以將方法和字段定義封裝起來,然後添加到類中
- 與類繼承不一樣的是,類繼承要求每個類都只能繼承一個超類,而一個類可以添加任意數量的特質。
- 特質的定義和抽象類的定義很像,但它是使用trait關鍵字
定義特質
trait 名稱 {
// 抽象字段
// 抽象方法
}
繼承特質
class 類 extends 特質1 with 特質2 {
// 字段實現
// 方法實現
}
- 使用extends來繼承trait(scala不論是類還是特質,都是使用extends關鍵字)
- 如果要繼承多個trait,則使用with關鍵字
trait作爲接口使用
trait作爲接口使用,與java的接口使用方法一樣。
示例 | 繼承單個trait
- 創建一個Logger特質,添加一個接受一個String類型參數的log抽象方法
- 創建一個ConsoleLogger類,繼承Logger特質,實現log方法,打印消息
- 添加main方法,創建ConsoleLogger對象,調用log方法
trait Logger {
// 抽象方法
def log(message:String)
}
class ConsoleLogger extends Logger {
override def log(message: String): Unit = println("控制檯日誌:" + message)
}
def main(args: Array[String]): Unit = {
val logger = new ConsoleLogger
logger.log("這是一條日誌")
}
示例 | 繼承多個trait
- 創建一個MessageSender特質,添加send方法
- 創建一個MessageReceiver特質,添加receive方法
- 創建一個MessageWorker實現這兩個特質
- 在main中調用,分別調用send方法、receive方法
trait MessageSender {
def send(msg:String)
}
trait MessageReceive {
def receive():String
}
class MessageWorker extends MessageSender with MessageReceive {
override def send(msg: String): Unit = println(s"發送消息:${msg}")
override def receive(): String = "你好!我叫一個好人!"
}
def main(args: Array[String]): Unit = {
val worker = new MessageWorker
worker.send("hello")
println(worker.receive())
}
示例 | object繼承trait
- 創建一個Logger特質,添加一個log抽象方法
- 創建一個ConsoleLogger的object,實現LoggerForObject特質,實現log方法,打印消息
- 編寫main方法,調用ConsoleLogger的log方法
trait Logger {
def log(message:String)
}
object ConsoleLogger extends Logger {
override def log(message: String): Unit = println("控制檯消息:" + message)
}
def main(args: Array[String]): Unit = {
ConsoleLogger.log("程序退出!")
}
三十九、特質 | 定義具體的方法
和類一樣,trait中還可以定義具體的方法
- 定義一個Logger特質,添加log實現方法
- 定義一個UserService類,實現Logger特質
- 添加add方法,打印"添加用戶"
3. 添加main方法
- 創建UserService對象實例
- 調用add方法
trait LoggerDetail {
// 在trait中定義具體方法
def log(msg:String) = println(msg)
}
class UserService extends LoggerDetail {
def add() = log("添加用戶")
}
object MethodInTrait {
def main(args: Array[String]): Unit = {
val userService = new UserService
userService.add()
}
}
四十、trait中定義具體的字段和抽象的字段
- 在trait中可以定義具體字段和抽象字段
- 繼承trait的子類自動擁有trait中定義的字段
- 字段直接被添加到子類中
通過trait來實現一個日誌輸出工具,該日誌工具可以自動添加日誌的日期
- 創建Logger特質
- 定義一個SimpleDateFormat字段,用來格式化日期(顯示到時間)
- 定義一個TYPE抽象字段,用於定義輸出的信息
- 創建一個log抽象方法,用於輸出日誌
- 創建ConsoleLogger類,實現TYPE抽象字段和log方法
- 添加main方法
- 創建ConsoleLogger類對象
- 調用log方法
trait Logger {
val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm")
def log(msg:String)
}
class ConsoleLogger extends Logger {
override def log(msg: String): Unit = {
val info = s"${sdf.format(new Date())}:控制檯消息:${msg}"
println(info)
}
}
def main(args: Array[String]): Unit = {
val logger = new ConsoleLogger()
logger.log("NullPointerException")
}
四十一、使用trait實現模板模式
要實現以下需求:
- 實現一個輸出日誌的功能
- 目前要求輸出到控制檯
- 將來可能會輸出到文件、輸出到Redis、或者更多的需求
如何實現將來不修改之前的代碼,來擴展現有功能呢?
定義
在一個特質中,具體方法依賴於抽象方法,而抽象方法可以放到繼承trait的子類中實現,這種設計方式也稱爲模板模式
在scala中,trait是可以定義抽象方法,也可以定義具體方法的
- trait中定義了一個抽象方法
- trait中定義了其他的幾個具體方法,會調用抽象方法
- 其他實現類可以來實現抽象方法
- 真正調用trait中具體方法的時候,其實會調用實現類的抽象方法實現
示例
- 編寫一個日誌輸出工具,分別有info、warn、error三個級別的日誌輸出
- 日誌輸出的方式要求設計爲可擴展的,例如:可以輸出到控制檯、將來也可以擴展輸出到文件、數據庫等
實現步驟
- 添加一個Logger特質
- 添加一個log抽象方法
- 添加一個info、warn、error具體方法,這幾個方法調用log抽象方法
- 創建ConsoleLogger類,實現Logger特質
- 添加main方法
- 創建ConsoleLogger類對象
- 分別調用info、warn、error方法輸出日誌
trait Logger {
def log(msg:String)
def info(msg:String) = log("INFO:" + msg)
def warn(msg:String) = log("WARN:" + msg)
def error(msg:String) = log("ERROR:" + msg)
}
class ConsoleLogger extends Logger {
override def log(msg: String): Unit = {
println(msg)
}
}
def main(args: Array[String]): Unit = {
val logger = new ConsoleLogger
logger.info("信息日誌")
logger.warn("警告日誌")
logger.error("錯誤日誌")
}
四十二、對象混入trait
scala中可以將trait混入到對象中,就是將trait中定義的方法、字段添加到一個對象中
val/var 對象名 = new 類 with 特質
示例
- 給一個對象添加一些額外的行爲
步驟
- 創建一個Logger特質
- 添加一個log實現方法,打印參數
- 創建一個UserService類
- 添加main方法
- 創建UserService對象,混入Logger特質
- 調用log方法
trait Logger {
def log(msg:String) = println(msg)
}
class UserService
def main(args: Array[String]): Unit = {
val service = new UserService with Logger
service.log("混入的方法")
}
四十三、trait實現調用鏈模式
我們如果要開發一個支付功能,往往需要執行一系列的驗證才能完成支付。例如:
- 進行支付簽名校驗
- 數據合法性校驗
- ...
如果將來因爲第三方接口支付的調整,需要增加更多的校驗規則,此時如何不修改之前的校驗代碼,來實現擴展呢?
責任鏈模式
trait調用鏈
類繼承了多個trait後,可以依次調用多個trait中的同一個方法,只要讓多個trait中的同一個方法在最後都依次執行super關鍵字即可。類中調用多個tait中都有這個方法時,首先會從最右邊的trait方法開始執行,然後依次往左執行,形成一個調用鏈條。
示例
實現一個模擬支付過程的調用鏈
步驟
1.定義一個HandlerTrait特質
- 定義一個具體的handler方法,打印"處理數據..."
2.定義一個DataValidHandlerTrait,繼承HandlerTrait特質
- 重寫handler方法,打印"驗證數據"
- 調用父特質的handler方法
3.定義一個SignatureValidHandlerTrait,繼承HandlerTrait特質
- 重寫Handler方法
- 打印"檢查簽名"
- 調用父特質的handler方法
4.創建一個PaymentService類
- 繼承DataValidHandlerTrait
- 繼承SignatureValidHandlerTrait
- 定義pay方法
打印"準備支付"
調用父特質的handler方法
5.添加main方法
- 創建PaymentService對象實例
- 調用pay方法
trait HandlerTrait {
def handle(data:String) = println("處理數據...")
}
trait DataValidHanlderTrait extends HandlerTrait {
override def handle(data:String): Unit = {
println("驗證數據...")
super.handle(data)
}
}
trait SignatureValidHandlerTrait extends HandlerTrait {
override def handle(data: String): Unit = {
println("校驗簽名...")
super.handle(data)
}
}
class PayService extends DataValidHanlderTrait with SignatureValidHandlerTrait {
override def handle(data: String): Unit = {
println("準備支付...")
super.handle(data)
}
}
def main(args: Array[String]): Unit = {
val service = new PayService
service.handle("支付參數")
}
// 程序運行輸出如下:
// 準備支付...
// 檢查簽名...
// 驗證數據...
// 處理數據...
四十四、trait繼承class
定義
trait也可以繼承class的。特質會將class中的成員都繼承下來。
示例
- 定義一個特質,繼承自一個class
步驟
1.創建一個MyUtils類,定義printMsg方法
2.創建一個Logger特質,繼承自MyUtils,定義log方法
3.創建一個Person類,添加name字段
- 繼承Logger特質
- 實現sayHello方法,調用log方法
4.添加main方法,創建一個Person對象,調用sayHello方法
class MyUtil {
def printMsg(msg:String) = println(msg)
}
trait Logger extends MyUtil {
def log(msg:String) = printMsg("Logger:" + msg)
}
class Person extends Logger {
def sayHello() = log("你好")
}
def main(args: Array[String]): Unit = {
val person = new Person
person.sayHello()
}