一、函數(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)點
- 可以以更少的代碼實現(xiàn)同樣的功能,可極大的提升生產(chǎn)效率
- 更容易編寫多并發(fā)或多線程的應(yīng)用,更易于編寫利用多核的應(yīng)用程序
- 可以幫助寫出健壯的代碼
- 更容易寫出易于閱讀、理解的優(yōu)雅代碼
函數(shù)式編程的基本特征
- 函數(shù)是一等公民:函數(shù)也有數(shù)據(jù)類型,函數(shù)與其他數(shù)據(jù)類型的變量或值一樣,處于平等地位,可以賦值給其它變量,也可以作為函數(shù)參數(shù),傳入另一個函數(shù),或者作為別的函數(shù)的返回值。
-
不可變數(shù)據(jù):所有的狀態(tài)(或變量)都是不可變的。你可以聲明一個狀態(tài),但是不能改變這個狀態(tài)。如果要變化,只能復(fù)制一個。
純函數(shù)式編程語言不使用任何可變數(shù)據(jù)結(jié)構(gòu)或變量。但在Scala等編程語言中,即支持不可變的數(shù)據(jù)結(jié)構(gòu)或變量,也支持可變的。 -
函數(shù)沒有 “副作用”:函數(shù)要保持獨立,一旦函數(shù)的輸入確定,輸出就是確定的,函數(shù)的執(zhí)行不會影響系統(tǒng)的狀態(tài),不會修改外部狀態(tài)。
如果函數(shù)沒有副作用,那函數(shù)的執(zhí)行就可以緩存起來了,一旦函數(shù)執(zhí)行過一次,如果再次執(zhí)行,當(dāng)輸入和前面一樣的情況下,就直接可以用前面執(zhí)行的輸出結(jié)果,就不用再次運算了,可大大提高程序運行的效率。 - 一切皆是表達式:在函數(shù)式編程語言中,每一個語句都是一個表達式,都會有返回值。
1.2 基本語法

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

沒有名字的函數(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ù)四要素:
- 函數(shù)調(diào)用自身
- 函數(shù)必須要有跳出的邏輯
- 函數(shù)的遞歸過程要逼近跳出邏輯
- 遞歸函數(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)用
- 值調(diào)用:把計算后的值傳入
println(3 + 4)
- 名調(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.直接在文件首行聲明,和java一樣
package com.alibaba.scalamaben.obj
// 2. 在代碼中嵌套聲明
package com {
package alibaba {
.....
}
}
// 這種方式可以使一個源文件中聲明多個 package; 子包的類可以直接訪問父包中的內(nèi)容,而無需導(dǎo)包
// 但是很少使用
- 包對象
可以為包定義同名的包對象,定義在包對象中的成員,作為其對應(yīng)包下所有 class 和 object 的共享變量,可以被直接訪問。
package object com{
val shareValue="share"
def shareMethod()={}
}
- 導(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() {
}
}
- Scala 中默認導(dǎo)入的包
scala._
scala.Predef._
java.lang._
- 權(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 類和對象
- 類的屬性
class User(var name: String, val age: Int, sex: String)
// var 修飾的 name 會有 scala 形式的 get/set 方法
// val 修飾的 age,由于不可變,只有 get 方法
// sex 則只是一個私有屬性,沒有對外使用的方法
// 1.直接在文件首行聲明,和java一樣
package com.alibaba.scalamaben.obj
// 2. 在代碼中嵌套聲明
package com {
package alibaba {
.....
}
}
// 這種方式可以使一個源文件中聲明多個 package; 子包的類可以直接訪問父包中的內(nèi)容,而無需導(dǎo)包
// 但是很少使用
可以為包定義同名的包對象,定義在包對象中的成員,作為其對應(yīng)包下所有 class 和 object 的共享變量,可以被直接訪問。
package object com{
val shareValue="share"
def shareMethod()={}
}
普通導(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() {
}
}
scala._
scala.Predef._
java.lang._
Scala 中屬性和方法的默認訪問權(quán)限為 'public',Scala 中不加修飾符即為 'public'
private 為私有權(quán)限,只在類的內(nèi)部和伴生對象中可用
protected 為受保護權(quán)限,Scala 中受保護權(quán)限更為嚴格,在同類、子類中可以訪問,同包內(nèi)無法訪問
private [ 包名 ] 增加包訪問權(quán)限,包名下的其他類也可以使用
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 方法,也無法在外部訪問。
- 類的方法
語法: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))
}
}
- 創(chuàng)建對象
語法:var / val 對象名 [: 類型] = new 類型 ( )
val 修飾的對象,不能改變對象的引用地址,可以改變對象的屬性的值。
var 修飾的對象,可以修改對象的引用和屬性值。
val person = new Person()
person.name = 'aa'
person.name = 'bb'
person = new Person() // person 不可變,故報錯
- 構(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 三大特征
- 封裝,同 java
- 繼承,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)用順序。
- 多態(tài)
父類(編譯時類型) = 子類(運行時類型)
子類的對象賦值給父類的引用
val person: Person = new Emp("李四", "23")
2.4 抽象屬性和抽象方法
- 抽象屬性和抽象方法
抽象類: 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")
}
}
- 抽象內(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 單例對象(伴生對象)
- 伴生對象與伴生類
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)造器。
伴生對象和伴生類必須位于同一文件中,伴生對象和伴生類可以相互訪問私有成員 - 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
- 小結(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)
- 特質(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")
}
}
- 特質(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("更好的獲取連接....")
}
}
- 疊加特質(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()
- 特質(zhì)中的具體字段
特質(zhì)中的字段可以是抽象的也可以是具體的
混入該特質(zhì)(或者繼承該特質(zhì))的類就具有了該字段,字段不是繼承,而是直接加入類,成為自己的字段。 - 特質(zhì)繼承類
Scala 中還有一種不太常用的手法: 特質(zhì)繼承類
將來這個被繼承的類會自動成為所有混入了該特質(zhì)的類的直接超類。
注:如果混入該特質(zhì)的類,已經(jīng)繼承了另一個類(A類),則要求A類是特質(zhì)超類的子類,否則就會出現(xiàn)了多繼承現(xiàn)象,發(fā)生錯誤 - 自身類型
當(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 補充
- 類型檢測和轉(zhuǎn)換
obj.isInstanceOf [ T ] 判斷 obj 是不是 T 類型
obj.asInstanceOf [ T ] 將 obj 強轉(zhuǎn)成 T 類型
classOf 獲取對象的類名 - 枚舉類和應(yīng)用類
枚舉類:需要繼承 Enumeration
應(yīng)用類:需要繼承 App - 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"
}
}