Scala 函數(shù)式編程與面向?qū)ο?/h2>

一、函數(shù)式編程

1.1 概述

函數(shù)式編程(FP,即Functional Programming)也是近幾年才逐漸為人們所知,但它并不是新的概念。函數(shù)式編程語言的鼻主Lisp語言的產(chǎn)生時間甚至比C語言還早,它擁有和面向?qū)ο缶幊蹋∣OP)幾乎等長的歷史。
函數(shù)式編程,其實就是以 純函數(shù) 的方式編寫代碼。
純函數(shù):一個函數(shù)在程序的執(zhí)行過程中除了根據(jù)輸入?yún)?shù)給出運算結(jié)果之外沒用其他影響,就可以說是沒有 副作用 的,我們就可以將這一類函數(shù)稱之為純函數(shù)。
純函數(shù)最核心的目的是為了編寫無副作用的代碼,它的很多特性,包括不變量,惰性求值等等都是為了這個目標(biāo)。
函數(shù)式編程的優(yōu)點

  1. 可以以更少的代碼實現(xiàn)同樣的功能,可極大的提升生產(chǎn)效率
  2. 更容易編寫多并發(fā)或多線程的應(yīng)用,更易于編寫利用多核的應(yīng)用程序
  3. 可以幫助寫出健壯的代碼
  4. 更容易寫出易于閱讀、理解的優(yōu)雅代碼

函數(shù)式編程的基本特征

  1. 函數(shù)是一等公民:函數(shù)也有數(shù)據(jù)類型,函數(shù)與其他數(shù)據(jù)類型的變量或值一樣,處于平等地位,可以賦值給其它變量,也可以作為函數(shù)參數(shù),傳入另一個函數(shù),或者作為別的函數(shù)的返回值。
  2. 不可變數(shù)據(jù):所有的狀態(tài)(或變量)都是不可變的。你可以聲明一個狀態(tài),但是不能改變這個狀態(tài)。如果要變化,只能復(fù)制一個。
    純函數(shù)式編程語言不使用任何可變數(shù)據(jù)結(jié)構(gòu)或變量。但在Scala等編程語言中,即支持不可變的數(shù)據(jù)結(jié)構(gòu)或變量,也支持可變的。
  3. 函數(shù)沒有 “副作用”:函數(shù)要保持獨立,一旦函數(shù)的輸入確定,輸出就是確定的,函數(shù)的執(zhí)行不會影響系統(tǒng)的狀態(tài),不會修改外部狀態(tài)。
    如果函數(shù)沒有副作用,那函數(shù)的執(zhí)行就可以緩存起來了,一旦函數(shù)執(zhí)行過一次,如果再次執(zhí)行,當(dāng)輸入和前面一樣的情況下,就直接可以用前面執(zhí)行的輸出結(jié)果,就不用再次運算了,可大大提高程序運行的效率。
  4. 一切皆是表達式:在函數(shù)式編程語言中,每一個語句都是一個表達式,都會有返回值。

1.2 基本語法

image.png

函數(shù)和方法的區(qū)別

object TestFunction {
    // (2)方法可以進行重載和重寫,程序可以執(zhí)行
    def main(): Unit = {
    }
    def main(args: Array[String]): Unit = {
        // (1)Scala語言的語法非常靈活,可以在任何的語法結(jié)構(gòu)中聲明任何的語法
        import java.util.Date
        new Date()
        // (2)函數(shù)沒有重載和重寫的概念,程序報錯
        def test(): Unit ={
            println("無參,無返回值")
        }
        test()
        def test(name:String):Unit={
            println()
        }
        //(3)scala中函數(shù)可以嵌套定義
        def test2(): Unit ={
            def test3(name:String):Unit={
                println("函數(shù)可以嵌套定義")
            }
        }
    }
}

1.3 函數(shù)的參數(shù)

// (1) 可變參數(shù)
def test(s: String*): Unit = {
    println(s)
}
// 有輸入?yún)?shù):輸出Array
test("Hello", "Scala")
// 無輸入?yún)?shù):輸出List()
test()

// (2) 如果參數(shù)列表中存在多個參數(shù),name可變參數(shù)一般放置在后面
def test2(name: String, s: String*): Unit = {
    println(name + "," + s)
}

// (3) 默認參數(shù),一般情況下,將有默認值的參數(shù)放置在參數(shù)列表的后面
def test3(name: String, age: Int = 30): Unit = {
    println(s"$name, $age")
}
// 如果要使用默認,在調(diào)用的時候,可以省略這個參數(shù)
test3("jerry")
// 如果參數(shù)傳遞了值,那么會覆蓋默認值
test3("tom", 20)

// (4) 命名參數(shù)
def test4(name: String, age: Int = 30, gender: String): Unit = {
    println(s"$name, $age, $gender")
}
// 可以指明參數(shù)名稱,進行傳參
test4("alex", gender = "male")

1.4 函數(shù)的至簡原則

  1. 函數(shù)體內(nèi)可以省略 return,Scala 會自動把最后一行代碼作為返回值
    若函數(shù)使用 return 關(guān)鍵字,那么函數(shù)就不能自行推斷了,需要聲明返回值類型
  2. 返回值如果能夠推斷出來,那么可以省略
  3. 函數(shù)體只有一行代碼,括號可以省略
  4. 若函數(shù)無參數(shù),聲明函數(shù)時可以省略小括號;若聲明函數(shù)時省略小括號,則調(diào)用該函數(shù)時,也需要省略小括號
  5. 若函數(shù)明確聲明 Unit,那么即使函數(shù)體中使用 return 關(guān)鍵字也不起作用
  6. Scala 如果想要自動推斷無返回值,可以省略等號
// 將無返回值的函數(shù)稱之為過程
def f7() {
  println("dalang")
}
  1. 如果不關(guān)心名稱,只關(guān)心邏輯處理,那么函數(shù)名(def)可以省略,省略名字的函數(shù)稱為匿名函數(shù)
  2. 純函數(shù):純函數(shù)天然的支持高并發(fā)!
    純函數(shù)的特點:1. 不產(chǎn)生副作用(常見的副作用:打印到控制臺,修改了外部變量的值,向磁盤寫入文件...)2. 引用透明(函數(shù)的返回值,只和形參有關(guān),和其它任何的值沒有關(guān)系)
  3. 惰性求值:類似于懶加載
val a = { 10 }  // 立即賦值
lazy val b = { 20 } // 惰性求值,調(diào)用時賦值一次,以后不用賦值
def c = 30  // 每次調(diào)用都會賦值一次

1.5 高階函數(shù)

參數(shù)或返回值為函數(shù)的函數(shù)稱為高階函數(shù)(高階算子)

//高階函數(shù) ———— 函數(shù)作為參數(shù)
def calculator(a: Int, b: Int, operater: (Int, Int) => Int): Int = {
    operater(a, b)
}
 //函數(shù)(求和)
def plus(x: Int, y: Int): Int = {
    x + y
}
// 函數(shù)(求積)
def multiply(x: Int, y: Int): Int = {
    x * y
}
//函數(shù)作為參數(shù)
println(calculator(2, 3, plus))
println(calculator(2, 3, multiply))

1.6 匿名函數(shù)

image.png

沒有名字的函數(shù)就是匿名函數(shù),直接通過函數(shù)字面量(lambda表達式:( ) => x + y)來表示匿名函數(shù)
匿名函數(shù)的簡寫:當(dāng)傳入的參數(shù)只被使用了一次時,可以使用 _ 指代,多個 _ 代表多個參數(shù)。

object TestFunction {
    //高階函數(shù) ———— 函數(shù)作為參數(shù)
    def calculator(a: Int, b: Int, operator: (Int, Int) => Int): Int = {
        operator(a, b)
    }
    //函數(shù)————求和
    def plus(x: Int, y: Int): Int = {
        x + y
    }
    def main(args: Array[String]): Unit = {
        //函數(shù)作為參數(shù)
        println(calculator(2, 3, plus))
        //匿名函數(shù)作為參數(shù)
        println(calculator(2, 3, (x: Int, y: Int) => x + y))
        //匿名函數(shù)簡寫形式
        println(calculator(2, 3, _ + _))
    }
}

1.7 函數(shù)閉包&柯里化

閉包:如果一個函數(shù), 訪問到了它的外部(局部)變量的值, 那么這個函數(shù)和他所處的環(huán)境, 稱為閉包

//外部變量
var x: Int = 10

//閉包
def f(x: Int, y: Int): Int = {
      x + y
}

函數(shù)柯里化:將接收多個參數(shù)的函數(shù)轉(zhuǎn)化成接受一個參數(shù)的函數(shù)過程,可以理解為把函數(shù)的參數(shù)列表的多個參數(shù), 變成多個參數(shù)列表一個參數(shù)的過程。

// 1.原始函數(shù),一次傳入兩個函數(shù)
def add1(a:Int, b:Int): Int = a + b
// 2.將原函數(shù)分解成兩層函數(shù),外函數(shù)傳入第一個參數(shù),內(nèi)函數(shù)使用外函數(shù)的參數(shù),同時還需要在傳入一個函數(shù),
// 這樣其實是將一次傳入兩個參數(shù)的函數(shù)解耦,變成內(nèi)外兩個每次接收一個參數(shù)的函數(shù)。
def add2(a: Int): Int => Int = {
    (b: Int) => a + b
}
// 3.第二步中add2(a:Int)返回一個函數(shù),那么直接將(b:Int)作為其參數(shù)列表就可以簡寫成如下:
def add3(a: Int)(b: Int) = a + b
// 同時,add3可以看做一個函數(shù),而 (a:Int)(b:Int) 是它的兩個函數(shù)列表,但是每次只接收一個參數(shù),這就是函數(shù)最終柯里化的狀態(tài)。
// 理解柯里化:將接收多個參數(shù)的函數(shù)轉(zhuǎn)化成接受一個參數(shù)的函數(shù)過程

1.8 遞歸函數(shù)

一個函數(shù)/方法在函數(shù)/方法體內(nèi)又調(diào)用了本身,我們稱之為遞歸調(diào)用
遞歸函數(shù)四要素:

  1. 函數(shù)調(diào)用自身
  2. 函數(shù)必須要有跳出的邏輯
  3. 函數(shù)的遞歸過程要逼近跳出邏輯
  4. 遞歸函數(shù)的返回值要手動聲明
// 求 10 的階乘
def factorial(n: Long): Long = {
  if (n == 1) 1
  else n * factorial(n - 1)
}

1.9 惰性函數(shù)

當(dāng)函數(shù)返回值被定義為 lazy 時,函數(shù)的執(zhí)行將被推遲,直到我們首次調(diào)用此值,該函數(shù)才會被執(zhí)行,這種函數(shù)稱之為惰性函數(shù)。

def main(args: Array[String]): Unit = {
    lazy val res = sum(10, 30)  // 注意:lazy 不能修飾 var 類型的變量
    println("----------------")
    println("res=" + res)
}

def sum(n1: Int, n2: Int): Int = {
    println("sum被執(zhí)行。。。")
    return n1 + n2
}

// 輸出結(jié)果
----------------
sum被執(zhí)行。。。
res=40

1.10 值調(diào)用&名調(diào)用

  1. 值調(diào)用:把計算后的值傳入
println(3 + 4)
  1. 名調(diào)用:把一段代碼傳入,調(diào)用時,運行代碼,調(diào)用幾次運行幾次
def main(args: Array[String]): Unit = {
  // 注意:這里調(diào)用時,要使用 a() 作為參數(shù)才可以代表a接收到的匿名函數(shù),而 a 函數(shù) 本身內(nèi)部什么都沒有定義
  foo(a())
}
// 定義 a 返回一個匿名函數(shù) () => {}
def a:() => Int = () => {
  println("f...")
  10
}
// 將 a 以名調(diào)用的方式傳入foo中,實際是把匿名函數(shù) () => {} 傳入 foo 中
def foo(a: => Int): Unit = {
  println(a)
  println(a)
  println(a)
}
// 輸出結(jié)果
f...
10
f...
10
f...
10

1.11 控制抽象

Scala 中可以自己定義類似于 if-else,while 的流程控制語句,即所謂的控制抽象。
提示:scala 中 { code...... } 結(jié)構(gòu)稱為代碼塊(block),可視為無參函數(shù),作為 =>Unit 類型的參數(shù)值。

def main(args: Array[String]): Unit = {
  
  var i =2
  // 調(diào)用自己寫的循環(huán),這里傳入兩段代碼
  // myWhile({i <= 100})({println(i += 1);i += 1}) 可行
  // 最終寫法:
  myWhile(i <= 100) {
    println(i += 1)
    i += 1
  }
}
// 使用名調(diào)用、柯里化傳入兩個段代碼,這兩段代碼都在調(diào)用時執(zhí)行
def myWhile (condition: => Boolean)(op: => Unit): Unit = {
  
  if (condition) {
    op
    myWhile(condition)(op)
  }
}

以上就是一個自定義循環(huán)函數(shù)。


二、面向?qū)ο?/h1>

2.1 Scala 的包

  1. 包的聲明方式
// 1.直接在文件首行聲明,和java一樣
package com.alibaba.scalamaben.obj
// 2. 在代碼中嵌套聲明
package com {
    package alibaba {
        .....
    }
}
// 這種方式可以使一個源文件中聲明多個 package; 子包的類可以直接訪問父包中的內(nèi)容,而無需導(dǎo)包
// 但是很少使用
  1. 包對象
    可以為包定義同名的包對象,定義在包對象中的成員,作為其對應(yīng)包下所有 class 和 object 的共享變量,可以被直接訪問。
package object com{
    val shareValue="share"
    def shareMethod()={}
}
  1. 導(dǎo)包
    普通導(dǎo)入、通配符導(dǎo)入、起別名導(dǎo)入、屏蔽類、指定多個類導(dǎo)入、局部導(dǎo)入
// 1.通配符導(dǎo)入util下的所有類
import java.util._
// 2.給類起別名,給 ArrayList 起別為JAL,防止類名沖突
import java.util.{ArrayList => JAL}
// 3.屏蔽類:導(dǎo)入util下的所有類,但不包含ArrayList
import java.util.{ArrayList => _, _}
// 4.導(dǎo)入多個類,指定導(dǎo)入某幾個類
import java.util.{HashSet, ArrayList}

object PckDemo2 {
    // 5.局部導(dǎo)入,在局部免去寫 List
    import java.util.List
    def foo() {
    }
}
  1. Scala 中默認導(dǎo)入的包
scala._
scala.Predef._
java.lang._
  1. 權(quán)限修飾符
    Scala 中屬性和方法的默認訪問權(quán)限為 'public',Scala 中不加修飾符即為 'public'
    private 為私有權(quán)限,只在類的內(nèi)部和伴生對象中可用
    protected 為受保護權(quán)限,Scala 中受保護權(quán)限更為嚴格,在同類、子類中可以訪問,同包內(nèi)無法訪問
    private [ 包名 ] 增加包訪問權(quán)限,包名下的其他類也可以使用

2.2 類和對象

  1. 類的屬性
class User(var name: String, val age: Int, sex: String)
// var 修飾的 name 會有 scala 形式的 get/set 方法
// val 修飾的 age,由于不可變,只有 get 方法
// sex 則只是一個私有屬性,沒有對外使用的方法

實際生產(chǎn)中,很多 java 框架會利用反射調(diào)用 get/set 方法,有時候為了兼容這些框架,會為 scala 的屬性設(shè)置 java 形式的 get和set 方法(通過@BeanProperty注解實現(xiàn))。

class User(@BeanProperty var name: String, @BeanProperty val age: Int, sex: String)
// var 類型的屬性會額外的生成 java 形式的 get/set 方法
// val 類型的屬性只會額外生成 java 形式的 get 方法
// 當(dāng)類中的屬性不加 var/val 時,該屬性默認為私有屬性,沒有公共的 get/set 方法,也無法在外部訪問。
  1. 類的方法
    語法:def 方法名 (參數(shù)列表) [: 返回值類型] = { 方法體 }
// 類中的方法
class Person {
    def sum(n1:Int, n2:Int) : Int = {
        n1 + n2
    }
}
// 對象中的方法
object Person {
    def main(args: Array[String]): Unit = {
        val person = new Person()
        println(person.sum(10, 20))
    }
}
  1. 創(chuàng)建對象
    語法:var / val 對象名 [: 類型] = new 類型 ( )
    val 修飾的對象,不能改變對象的引用地址,可以改變對象的屬性的值。
    var 修飾的對象,可以修改對象的引用和屬性值。
val person = new Person()
person.name = 'aa'
person.name = 'bb'
person = new Person() // person 不可變,故報錯
  1. 構(gòu)造器
    Scala 類的構(gòu)造器包含:主構(gòu)造器 和 輔助構(gòu)造器
    主構(gòu)造器:
    1.主構(gòu)造器只能有一個,當(dāng)沒有形參時可以省略 ( )
    2.主構(gòu)造器的 形參自動成為類的屬性
    輔助構(gòu)造器:
    1.輔助構(gòu)造器的函數(shù)名為,多個輔助構(gòu)造器之間構(gòu)成重載
    2.輔助構(gòu)造器 首行必須直接或間接的調(diào)用主構(gòu)造器
    3.輔助構(gòu)造器調(diào)用其他輔助構(gòu)造器的收后面的調(diào)用前面的
object ObjDemo1 {
  def main(args: Array[String]): Unit = {
    val person2 = new Person(18)
  }
}
class Person (val name: String, var age: Int, sex: String) {
  var name: String = "lisi"
  var age: Int = _          // 默認值 0
  def this(age: Int) {
    this()
    this.age = age
    println("輔助構(gòu)造器")
  }

  def this(age: Int, sex: String) {
    this(age)
    // this.name = name         柱構(gòu)造器中 val 修飾,無法修改
    this.sex = sex
  }
  println("主構(gòu)造器")       // 主構(gòu)造中的語句先被執(zhí)行
}

2.3 三大特征

  1. 封裝,同 java
  2. 繼承,Scala 是單繼承,函數(shù)和屬性都是動態(tài)綁定,同時支持函數(shù)和屬性的覆寫
    函數(shù)的覆寫:必須添加 override 關(guān)鍵字(java 中可以省略)
    屬性的覆寫:只支持覆寫 val 類型,而不支持 var 類型
    繼承的構(gòu)造器調(diào)用:只有子類的主構(gòu)造器才有權(quán)利去調(diào)用父類的構(gòu)造器
object Test {
  def main(args: Array[String]): Unit = {
    new Emp("z3", 11, 1001)
  }
}

class Person(nameParam: String) {
  var name = nameParam
  var age: Int = _
  def this(nameParam: String, ageParam: Int) {
    this(nameParam)
    this.age = ageParam
    println("父類輔助構(gòu)造器")
  }
  println("父類主構(gòu)造器")
}

class Emp(nameParam: String, ageParam: Int) extends Person(nameParam, ageParam) {
  var empNo: Int = _
  def this(nameParam: String, ageParam: Int, empNoParam: Int) {
    this(nameParam, ageParam)
    this.empNo = empNoParam
    println("子類的輔助構(gòu)造器")
  }
  println("子類主構(gòu)造器")
}
----------------輸出
父類主構(gòu)造器
父類輔助構(gòu)造器
子類主構(gòu)造器
子類的輔助構(gòu)造器

觀察以上例子在創(chuàng)建子類對象時,構(gòu)造器的調(diào)用順序。

  1. 多態(tài)
    父類(編譯時類型) = 子類(運行時類型)
    子類的對象賦值給父類的引用
val person: Person = new Emp("李四", "23")

2.4 抽象屬性和抽象方法

  1. 抽象屬性和抽象方法
    抽象類: abstract class Person {} // 通過 abstract 關(guān)鍵字標(biāo)記抽象類
    抽象方法: val/var name: String // 一個屬性沒有初始化,就是抽象屬性
    抽象方法: def hello(): String // 只聲明,沒有函數(shù)體
    覆寫抽象方法時可以不加 override
abstract class Person {
  val name: String
  var age: Int
  def hello(): Unit
}

class Teacher extends Person {
  // 覆寫 val 類型需要加 override
  override val name: String = "lzc"
  var age = 1
  // 覆寫抽象方法可以不加 override
  def hello(): Unit = {
    println(s"hello $name")
  }
}
  1. 抽象內(nèi)部類(匿名子類)
    和 Java 一樣,Scala 可以通過包含帶有定義或重寫的代碼的方式創(chuàng)建一個匿名的子類
abstract class Person {
  val name: String
  def hello(): Unit
}
object Test {
  def main(args: Array[String]): Unit = {
    // 創(chuàng)建匿名內(nèi)部類的對象,這個類是 Person 的實現(xiàn)類
    val person = new Person {
      override val name: String = "teacher"
      override def hello(): Unit = println("hello teacher")
    }
  }
}

2.5 單例對象(伴生對象)

  1. 伴生對象與伴生類
    Scala語言是完全面向?qū)ο蟮恼Z言,所以并沒有靜態(tài)的操作(即在Scala中沒有靜態(tài)的概念)。而是產(chǎn)生了一種特殊的對象來模擬靜態(tài)類對象,該對象為單例對象。若在一個文件中單例對象名與類名一致,則該單例對象與這個類與互為 伴生對象和伴生類,這個類的所有“靜態(tài)”內(nèi)容都可以放置在它的伴生對象中聲明。
    因此,在 Scala 中, 單例模式已經(jīng)在語言層面完成了解決。創(chuàng)建一個單例要使用關(guān)鍵字 object,因為不能實例化一個單例對象,所以不能傳遞參數(shù)給它的構(gòu)造器。
    伴生對象和伴生類必須位于同一文件中,伴生對象和伴生類可以相互訪問私有成員
  2. apply 方法
    使用伴生對象 ( 參數(shù) )的形式,其實是在調(diào)用伴生對象的 apply(參數(shù)) 的方法
    通過伴生對象的 apply 方法,可以實現(xiàn)不使用 new 方法來創(chuàng)建對象。
    apply方法 可以重載
object SingleDemo1 {
  def main(args: Array[String]): Unit = {
    // 默認調(diào)用 aplly 方法,不使用new創(chuàng)建對象
    Car("red", "JAPAN")
    Car("blue")
  }
}
// 伴生對象
object Car {
  def apply(color: String, country: String):Car = {
    val car = new Car(color, country)   // 調(diào)用伴生類的私有化構(gòu)造器
    car  // 返回對象
  }
  // 重載的 apply 方法
  def apply(color: String): Car = new Car(color)    // 調(diào)用伴生類的私有化構(gòu)造器
}
// 伴生類,參數(shù)列表前加 private 私有化主構(gòu)造器
class Car private (val color: String, country: String = "CHNIA"){
  println(s"a $color Car made in $country")
}
---------------- 輸出
a red Car made in JAPAN
a blue Car made in CHNIA
  1. 小結(jié)
    1.Scala 中伴生對象采用 object 關(guān)鍵字聲明,所謂的伴生對象其實就是類的靜態(tài)方法和靜態(tài)屬性的集合
    2.伴生對象對應(yīng)的類稱之為伴生類,伴生對象的名稱應(yīng)該和伴生類名一致,且在同一個源碼文件中
    3.伴生對象中的屬性和方法都可以通過伴生對象名(類名)直接調(diào)用訪問
    4.類和伴生對象之間可以互相訪問對方的私有成員
    5.apply 方法,可以實現(xiàn)不使用 new 方法來創(chuàng)建對象

2.6 特質(zhì)(trait)

  1. 特質(zhì)的基本使用
    Scala 是純面向?qū)ο蟮恼Z言,在 Scala 中,沒有接口,采用特質(zhì) trait(特征, 特質(zhì))來代替接口的概念,當(dāng)多個類具有相同的特質(zhì)時,就可以將這個特質(zhì)獨立出來,采用關(guān)鍵字trait聲明。
    特質(zhì)可以有抽象方法, 也可以有實體方法, 相比抽象類最大的優(yōu)點是 特質(zhì)可以實現(xiàn)多繼承, 抽象類是只能實現(xiàn)單繼承。
    特質(zhì)可以有非抽象方法
    一個類已經(jīng)繼承了一個類,或已經(jīng)混入了一個特質(zhì),要再混入一個特質(zhì)時應(yīng)該使用 with
    覆寫特質(zhì)的方法時可以不加 override
object TraitDemo {
  def main(args: Array[String]): Unit = {
    val dog = new Dog
    dog.move("lag")
  }
}
trait Animal {
  // 1.特質(zhì)也可以有非抽象的方法
  def run(useWhat: String): Unit = {
    println(s"an Animal run in $useWhat")
  }
}
trait Creature {
  def move(useWhat: String)
}
// 2.已經(jīng)繼承了一個類,或已經(jīng)混入了一個特質(zhì),要再混入一個特質(zhì)時應(yīng)該使用 with
class Dog extends Animal with Creature {
  // 3.覆寫特質(zhì)的方法時可以不加 override
  def move(useWhat: String): Unit = {
    println(s"a dog move in $useWhat")
  }
}
  1. 特質(zhì)的動態(tài)混入
    除了可以在類聲明時繼承特質(zhì)以外,還可以在 構(gòu)建對象時混入特質(zhì),擴展目標(biāo)類的功能。
    動態(tài)混入是 Scala 特有的方式,動態(tài)混入可以在不影響原有的繼承關(guān)系的基礎(chǔ)上,給指定的類擴展功能
object TraitDemo2 {
  def main(args: Array[String]): Unit = {
    val mysql = new Mysql with BetterConnectDB      // 創(chuàng)建 Mysql 對象的時候, 指定一個新的特質(zhì)
    mysql.connectToMysql()
    // 如果再創(chuàng)建新的對象, 也可以換另外的特質(zhì) ———— 這就叫動態(tài)混入
  }
}

trait ConnectToDB { // 這個特質(zhì)用來連接數(shù)據(jù)庫
  def getConn() {}  // 什么都不做的實現(xiàn)方法
}

class Mysql extends ConnectToDB {   // 連接到Mysql
  def connectToMysql(): Unit = {
    // 獲取連接
    getConn()
    //其他代碼
  }
}
// 上面的 getConn其實什么都沒有做, 感覺沒有什么用
// 但是我們創(chuàng)建 Mysql對象的時候, 可以給他指定一個更好的特質(zhì)
trait BetterConnectDB extends ConnectToDB { // 一個更好的特質(zhì)
  override def getConn(): Unit = { // 注意這個時候需要添加override
    println("更好的獲取連接....")
  }
}
  1. 疊加特質(zhì)
    構(gòu)建對象的同時如果混入多個特質(zhì),稱之為疊加特質(zhì)
object OverflowTrait {
  def main(args: Array[String]): Unit = {
    val test = new Test with A with B with C    // 疊加特質(zhì)
    test.foo()  // 最先調(diào)用 C 中的 foo 方法(C 又會掉 B中的 foo 一次類推,直到調(diào)用到 Fater 中的 foo)
  }
}

class Test { }
trait Father{
    def foo(): Unit ={
    println("Father...")
  }}
trait A extends Father{
    override def foo(): Unit = { // 以下省略每個 trait 中的 foo 方法(自行腦補)
    println("A...")
    super.foo()     // 不想按照順序向上找, 可以直接指定該特質(zhì)的直接父特質(zhì) super[Father].foo()
  }}
trait B extends Father{
    override def foo(): Unit = {
    println("B...")
    super.foo()
  }}
trait C extends Father{
    override def foo(): Unit = {
    println("C...")
    super.foo()
  }}
---------------輸出
C...
B...
A...
Fater...

一個對象混入多個特質(zhì),且特質(zhì)的構(gòu)建順序(從上到下)和混入的順序一致(從左到右),對象中的 foo 方法一定是最后一個特質(zhì)的方法。
如果不想按照順序向上找, 可以直接指定該特質(zhì)的直接父特質(zhì)super[Father].foo()

  1. 特質(zhì)中的具體字段
    特質(zhì)中的字段可以是抽象的也可以是具體的
    混入該特質(zhì)(或者繼承該特質(zhì))的類就具有了該字段,字段不是繼承,而是直接加入類,成為自己的字段。
  2. 特質(zhì)繼承類
    Scala 中還有一種不太常用的手法: 特質(zhì)繼承類
    將來這個被繼承的類會自動成為所有混入了該特質(zhì)的類的直接超類。
    注:如果混入該特質(zhì)的類,已經(jīng)繼承了另一個類(A類),則要求A類是特質(zhì)超類的子類,否則就會出現(xiàn)了多繼承現(xiàn)象,發(fā)生錯誤
  3. 自身類型
    當(dāng)特質(zhì)A 繼承類B 的時候, 編譯器能夠確保的是所有混入該特質(zhì)A 的類都認類B 作為自己的超類。
    Scala 還有另外一套機制也可以保證這一點: 自身類型(self type)
trait Logger {
  // 聲明該特質(zhì)就是 Exception,后面的 getMessage 才可以調(diào)用
  this: Exception =>    // 表示混入該特質(zhì)的類必須繼承 Exception 或 Exception 的子類
  def log(): Unit = {
    println(getMessage)
  }
}

class Console extends Exception with Logger {
}

2.7 補充

  1. 類型檢測和轉(zhuǎn)換
    obj.isInstanceOf [ T ] 判斷 obj 是不是 T 類型
    obj.asInstanceOf [ T ] 將 obj 強轉(zhuǎn)成 T 類型
    classOf 獲取對象的類名
  2. 枚舉類和應(yīng)用類
    枚舉類:需要繼承 Enumeration
    應(yīng)用類:需要繼承 App
  3. Type 定義新類型
    使用 type 關(guān)鍵字可以定義新的數(shù)據(jù)數(shù)據(jù)類型名稱,本質(zhì)上就是類型的一個別名
object Test {
    def main(args: Array[String]): Unit = {        
        type S = String     // 將 String 類型定義為 S 類型
        var v: S = "abc"
        def test(): S = "xyz"
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容