8.1 靜態屬性和靜態方法 8.1.1 靜態屬性-提出問題 有一群小孩在玩堆雪人,不時有新的小孩加入,請問如何知道現在共有多少人在玩?請使用面向對象的思想,編寫程式解決 8.1.2 基本介紹 -Scala中靜態的概念-伴生對象 Scala語言是完全面向對象(萬物皆對象)的語言,所以並沒有靜態的操作( ...
8.1 靜態屬性和靜態方法
8.1.1 靜態屬性-提出問題
有一群小孩在玩堆雪人,不時有新的小孩加入,請問如何知道現在共有多少人在玩?請使用面向對象的思想,編寫程式解決
8.1.2 基本介紹
-Scala中靜態的概念-伴生對象
Scala語言是完全面向對象(萬物皆對象)的語言,所以並沒有靜態的操作(即在Scala中沒有靜態的概念)。但是為了能夠和Java語言交互(因為Java中有靜態概念),就產生了一種特殊的對象來模擬類對象,我們稱之為類的伴生對象。這個類的所有靜態內容都可以放置在它的伴生對象中聲明和調用
8.1.3 伴生對象的快速入門
object boke_demo01 { def main(args: Array[String]): Unit = { println(ScalaPerson.sex) //true 在底層等價於 ScalaPerson$.MODULE$.sex() ScalaPerson.sayHi() //在底層等價於 ScalaPerson$.MODULE$.sayHi() } } //說明 //1. 當在同一個文件中,有 class ScalaPerson 和 object ScalaPerson //2. class ScalaPerson 稱為伴生類,將非靜態的內容寫到該類中 //3. object ScalaPerson 稱為伴生對象,將靜態的內容寫入到該對象(類) //4. class ScalaPerson 編譯後底層生成 ScalaPerson類 ScalaPerson.class //5. object ScalaPerson 編譯後底層生成 ScalaPerson$類 ScalaPerson$.class //6. 對於伴生對象的內容,我們可以直接通過 ScalaPerson.屬性 或者方法 //伴生類 class ScalaPerson { // var name: String = _ } //伴生對象 object ScalaPerson { // var sex: Boolean = true def sayHi(): Unit = { println("object ScalaPerson sayHI~~") } }
-對快速入門的案例的源碼分析
8.1.4 伴生對象的小結
1) Scala中伴生對象採用object關鍵字聲明,伴生對象中聲明的全是“靜態”內容,可以通過伴生對象名稱直接調用
2) 伴生對象對應的類稱之為伴生類,伴生對象的名稱應該和伴生類名一致
3) 伴生對象中的屬性和方法都可以通過伴生對象名(類名)直接調用訪問
4) 從語法角度來講,所謂的伴生對象其實就是類的靜態方法和成員的集合
5) 從技術角度來講,Scala還是沒有生成靜態的內容,只不過是將伴生對象生成了一個新的類,實現屬性和方法調用[反編譯看源碼]
6) 從底層原理看,伴生對象實現靜態特性是依賴 public static final MOUDLE$ 實現的
7) 伴生對象的聲明應該和伴生類的聲明在同一個源碼文件中(如果不在同一個文件中會運行錯誤),但是如果沒有伴生類,也就沒有所謂的伴生對象了,所以放在哪裡就無所謂了
8) 如果 class A 獨立存在,那麼A就是一個類,如果 Object A 獨立存在,那麼A就是一個“靜態”性質的對象[即類對象],在 Object A 中聲明的屬性和方法可以通過 A.屬性和A.方法 來實現調用
9) 當一個文件中,存在半生類和伴生對象時,文件的圖標會發生變化
8.1.5 最佳實踐-使用伴生對象完成小孩堆雪人游戲
設計一個var total Int 表示總人數,我們在創建一個小孩時,就把total加1,並且total是所有對象共用的就ok了,使用伴生對象來解決
object boke_demo01 { def main(args: Array[String]): Unit = { //創建三個小孩 val child0 = new Child("鐵蛋") val child1 = new Child("狗蛋") val child2 = new Child("熊大") Child.joinGame(child0) Child.joinGame(child1) Child.joinGame(child2) Child.showNum() } } class Child(cName: String) { var name = cName } object Child { //統計共有多少小孩的屬性 var totalChildNum = 0 def joinGame(child: Child): Unit = { printf("%s 小孩加入了游戲\n", child.name) //totalChildNum 加1 totalChildNum += 1 } def showNum(): Unit = { printf("當前有%d小孩玩游戲\n", totalChildNum) } }
8.1.6 伴生對象-apply方法
在伴生對象中定義apply方法,可以實現:類名(參數)方式來創建對象實例
object boke_demo01 { def main(args: Array[String]): Unit = { val list = List(1, 2, 5) println(list) val pig = new Pig("狗蛋") //使用apply方法來創建對象 val pig2 = Pig("鐵蛋") //自動 apply(pName: String) val pig3 = Pig() // 自動觸發 apply() println("pig2.name=" + pig2.name) //小黑豬 println("pig3.name=" + pig3.name) //匿名豬豬 } } //案例演示apply方法. class Pig(pName: String) { var name: String = pName } object Pig { //編寫一個apply def apply(pName: String): Pig = new Pig(pName) def apply(): Pig = new Pig("匿名") }
8.2 單例對象
這個部分將在Scala設計模式專題進行介紹
8.3 介面
8.3.1 回顧Java介面
-聲明介面
interface介面名
-實現介面
class 類名 implements 介面1,介面2
-Java介面的使用小結
1) 在Java中,一個類可以實現多個介面
2) 在Java中,介面之間支持多繼承
3) 介面中屬性都是常量
4) 介面中的方法都試抽象的
8.3.2 Scala介面的介紹
1) 從面向對象來看,介面並不屬於面向對象的範疇,Scala是純面向對象的語言,在Scala中,沒有介面
2) Scala語言中,採用特質trait(特征)來代替介面的概念,也就是說,多個類具有相同的特質(特征)時,就可以將這個特質(特征)獨立出來,採用關鍵字trait聲明。理解trait等價於(interface+abstract class)
3) Scala繼承特質(trait)的示意圖
8.3.3 trait的聲明
trait 特質名 {
trait 體
}
1) trait 命名 一般首字母大寫 Cloneable,Serializable
object T1 extends Serializable {
}
Serializable:就是Scala的一個特質
-在Scala中,Java中的介面可以當做特質使用
object boke_demo01 { def main(args: Array[String]): Unit = { } } //trait Serializable extends Any with java.io.Serializable //在scala中,java的介面都可以當做trait來使用(如上面的語法) object T1 extends Serializable { } object T2 extends Cloneable { }
8.3.4 Scala中trait的使用
一個類具有某種特質(特征),就意味著這個類滿足了這個特質(特征)的所有要素,所以在使用時,也採用了extends關鍵字,如果有多個特質或存在父類,那麼需要採用with關鍵字連接
1) 沒有父類
class 類名 extends 特質1 with 特質2 with 特質3...
2) 有父類
class 類名 extends 父類 with 特質1 with 特質2 with 特質3...
8.4 特質(trait)
8.4.1 特質的快速入門案例
Scala引入trait特質,第一可以替代Java的介面,第二也是對單繼承機制的一種補充
8.4.2 案例代碼
object boke_demo01 { def main(args: Array[String]): Unit = { val c = new C() val f = new F() c.getConnect() // 連接mysql資料庫... f.getConnect() // 連接oracle資料庫.. } } //按照要求定義一個trait trait Trait { //定義一個規範 def getConnect() } //先將六個類的關係寫出 class A {} class B extends A {} class C extends A with Trait { override def getConnect(): Unit = { println("連接mysql資料庫...") } } class D {} class E extends D {} class F extends D with Trait { override def getConnect(): Unit = { println("連接oracle資料庫..") } }
8.4.3 特質trait的再說明
1) Scala提供了特質(trait),特質可以同時擁有抽象方法和具體方法,一個類可以實現/繼承多個特質
object boke_demo01 { def main(args: Array[String]): Unit = { //創建sheep val sheep = new Sheep sheep.sayHi() sheep.sayHello() } } //當一個trait有抽象方法和非抽象方法時 //1. 一個trait在底層對應兩個 Trait.class 介面 //2. 還對應 Trait$class.class Trait$class抽象類 trait Trait { //抽象方法 def sayHi() //實現普通方法 def sayHello(): Unit = { println("say Hello~~") } } //當trait有介面和抽象類是 //1.class Sheep extends Trait 在底層 對應 //2.class Sheep implements Trait //3.當在 Sheep 類中要使用 Trait的實現的方法,就通過 Trait$class class Sheep extends Trait { override def sayHi(): Unit = { println("小羊say hi~~") } }
2) 特質中沒有實現的方法就是抽象方法。類通過extends繼承特質,通過with關鍵字可以繼承多個特質
3) 所有的Java介面都可以當做Scala特質使用
8.4.4帶有特質的對象,動態混入
1) 除了可以在類聲明時繼承特質以外,還可以在構建對象時混入特質,擴展目標類的功能
2) 此種方法也可以應用於對抽象類功能進行擴展
3) 動態混入是Scala特有的方式(Java沒有動態混入),可在不修改類聲明/定義的情況下,擴展類的功能,非常的靈活,耦合性低
4) 動態混入可以在不影響原有的繼承關係的基礎上,給指定的類擴展功能
5) 同時要註意動態混入時,如果抽象類有抽象方法,如何混入
6) 案例演示
object boke_demo01 { def main(args: Array[String]): Unit = { //在不修改類的定義基礎,讓它們可以使用trait方法 val oracleDB = new OracleDB with Operate oracleDB.insert(100) // val mySQL = new MySQL with Operate mySQL.insert(200) //如果一個抽象類有抽象方法,如何動態混入特質 val mySql_ = new MySQL_ with Operate { override def say(): Unit = { println("say") } } mySql_.insert(999) mySql_.say() } } trait Operate { //特質 def insert(id: Int): Unit = { //方法(實現) println("插入數據 = " + id) } } class OracleDB { //空 } abstract class MySQL { //空 } abstract class MySQL_ { //空 def say() }
-在Scala中創建對象的4種方式
1) new 對象
2) apply 創建
3) 匿名子類方式
4) 動態混入
8.4.5 疊加特質
-基本介紹
構建對象的同時如果混入多個特質,稱之為疊加特質,那麼特質聲明順序從左到右,方法執行順序從右到左
-疊加特質應用案例
目的:分析疊加特質時,對象的構建順序,和執行方法的順序
案例演示:
object boke_demo01 { def main(args: Array[String]): Unit = { //說明 //1. 創建 MySQL實例時,動態的混入 DB 和 File //研究第一個問題,當我們創建一個動態混入對象時,其順序是怎樣的 //總結一句話 //Scala在疊加特質的時候,會首先從後面的特質開始執行(即從左到右) //1.Operate... //2.Data //3.DB //4.File val mysql = new MySQL with DB with File println(mysql) //研究第2個問題,當我們執行一個動態混入對象的方法,其執行順序是怎樣的 //順序是,(1)從右到左開始執行 , (2)當執行到super時,是指的左邊的特質 (3) 如果左邊沒有特質了,則super就是父特質 //1. 向文件" //2. 向資料庫 //3. 插入數據 100 mysql.insert(100) println("===================================================") //練習題 val mySQL = new MySQL with File with DB mySQL.insert(999) //構建順序 //1.Operate... //2.Data //3.File //4.DB //執行順序 //1. 向資料庫 //2. 向文件 //3. 插入數據 = 999 } } trait Operate { //特點 println("Operate...") def insert(id: Int) //抽象方法 } trait Data extends Operate { //特質,繼承了Operate println("Data") override def insert(id: Int): Unit = { //實現/重寫 Operate 的insert println("插入數據 = " + id) } } trait DB extends Data { //特質,繼承 Data println("DB") override def insert(id: Int): Unit = { // 重寫 Data 的insert println("向資料庫") super.insert(id) } } trait File extends Data { //特質,繼承 Data println("File") override def insert(id: Int): Unit = { // 重寫 Data 的insert println("向文件") //super.insert(id) //調用了insert方法(難點),這裡super在動態混入時,不一定是父類 //如果我們希望直接調用Data的insert方法,可以指定,如下 //說明:super[?] ?的類型,必須是當前的特質的直接父特質(超類) super[Data].insert(id) } } class MySQL {} //普通類
-疊加特質註意事項和細節
1) 特質聲明順序從左到右
2) Scala 在執行疊加對象的方法時,會首先從後面的特質(從右向左)開始執行
3) Scala 中特質中如果調用 super,並不是表示調用父特質的方法,而是向前面(左邊)繼續 查找特質,如果找不到,才會去父特質查找
4) 如果想要調用具體特質的方法,可以指定:super[特質].xxx(...).其中的泛型必須是該特質的直接超類類型
8.4.6 當作富介面使用的特質
富介面:即該特質中既有抽象方法,又有非抽象方法
trait Operate { def insert(id: Int) //抽象 def pageQuery(pageno: Int, pagesize: Int): Unit = { //實現 println("分頁查詢") } }
8.4.7 特質中的具體欄位
特質中可以定義具體欄位,如果初始化了就是具體欄位,如果不初始化就是抽象欄位,混入該特質的類就具有了該欄位,欄位不是繼承,而是直接加入類,成為自己的欄位
object boke_demo01 { def main(args: Array[String]): Unit = { val mySQL = new MySQL with DB { override var sal = 10 } } } trait DB { var sal: Int //抽象欄位 var opertype: String = "insert" def insert(): Unit = { } } class MySQL {}
-反編譯後的代碼
8.4.8 特質中的抽象欄位
特質中未被初始化的欄位在具體的子類中必須被重寫
8.4.9 特質構造順序
-介紹
特質也是有構造器的,構造器中的內容由“欄位的初始化”和一些其他語句構成
-第一種特質構造順序(聲明類的同時混入特質)
1) 調用當前類的超類構造器
2) 第一個特質的父特質構造器
3) 第一個特質構造器
4) 第二個特質構造器的父特質構造器,如果已經執行過就不再執行
5) 第二個特質構造器
6) ......重覆4,5的步驟(如果有第3個,第4個特質)
7) 當前類構造器
-第二種特質構造順序(在構建對象時,動態混入特質)
1) 調用當前類的超類構造器
2) 當前類構造器
3) 第一個特質構造器的父特質構造器
4) 第一個特質構造器
5) 第二個特質構造器的父特質構造器,如果已經執行過就不再執行
6) 第二個特質構造器
7) ......重覆4,5的步驟(如果有第3個,第4個特質)
8) 當前類構造器
-兩種方式對構造順序的影響
1) 第一種方式實際是構建類對象,在混入特質時,該對象還沒有創建
2) 第二種方式實際是構造匿名子類,可以理解成在混入特質時,對象已經創建了
-案例演示
object boke_demo01 { def main(args: Array[String]): Unit = { //這時FF是這樣 形式 class FF extends EE with CC with DD /* 調用當前類的超類構造器 第一個特質的父特質構造器 第一個特質構造器 第二個特質構造器的父特質構造器, 如果已經執行過,就不再執行 第二個特質構造器 .......重覆4,5的步驟(如果有第3個,第4個特質) 當前類構造器 [案例演示] */ //1. E... //2. A... //3. B.... //4. C.... //5. D.... //6. F.... val ff1 = new FF() println(ff1) //這時我們是動態混入 /* 先創建 new KK 對象,然後再混入其它特質 調用當前類的超類構造器 當前類構造器 第一個特質構造器的父特質構造器 第一個特質構造器. 第二個特質構造器的父特質構造器, 如果已經執行過,就不再執行 第二個特質構造器 .......重覆5,6的步驟(如果有第3個,第4個特質) 當前類構造器 [案例演示] */ //1. E... //2. K.... //3. A... //4. B //5. C //6. D println("=======================") val ff2 = new KK with CC with DD println(ff2) } } trait AA { println("A...") } trait BB extends AA { println("B....") } trait CC extends BB { println("C....") } trait DD extends BB { println("D....") } class EE { //普通類 println("E...") } class FF extends EE with CC with DD { //先繼承了EE類,然後再繼承CC 和DD println("F....") } class KK extends EE { //KK直接繼承了普通類EE println("K....") }
8.4.10 擴展類的特質
-特質可以繼承類,以用來拓展該特質的一些功能
trait LoggedException extends Exception { def log(): Unit = { println(getMessage()) // 方法來自於Exception類 } }
-所有混入該特質的類,會自動成為那個特質所繼承的超類的子類
//1. LoggedException 繼承了 Exception //2. LoggedException 特質就可以 Exception 功能 trait LoggedException extends Exception { def log(): Unit = { println(getMessage()) // 方法來自於Exception類 } }
-如果混入該特質的類,已經繼承了另一個類(A類),則要求A類是特質超類的子類,否則就會出現了多繼承現象,發生錯誤
object boke_demo01 { def main(args: Array[String]): Unit = { println("h~~") } } //說明 //1. LoggedException 繼承了 Exception //2. LoggedException 特質就可以 Exception 功能 trait LoggedException extends Exception { def log(): Unit = { println(getMessage()) // 方法來自於Exception類 } } //因為 UnhappyException 繼承了 LoggedException //而 LoggedException 繼承了 Exception //UnhappyException 就成為 Exception子類 class UnhappyException extends LoggedException { // 已經是Exception的子類了,所以可以重寫方法 override def getMessage = "錯誤消息!" } // 如果混入該特質的類,已經繼承了另一個類(A類),則要求A類是特質超類的子類, // 否則就會出現了多繼承現象,發生錯誤。 class UnhappyException2 extends IndexOutOfBoundsException with LoggedException { // 已經是Exception的子類了,所以可以重寫方法 override def getMessage = "錯誤消息!" } class CCC {} //錯誤的原因是 CCC 不是 Exception子類 //class UnhappyException3 extends CCC with LoggedException{ // // 已經是Exception的子類了,所以可以重寫方法 // override def getMessage = "錯誤消息!" //}
8.4.11 自身類型
-說明
自身類型:主要是為瞭解決特質的迴圈依賴問題,同時可以確保特質在不擴展某個類的情況下,依然可以做到限制混入該特質的類的類型
-應用案例
舉例說明自身類型特質,以及如何使用自身類型特質
object boke_demo01 { def main(args: Array[String]): Unit = { } } //Logger就是自身類型特質,當這裡做了自身類型後,那麼 // trait Logger extends Exception,要求混入該特質的類也是 Exception子類 trait Logger { // 明確告訴編譯器,我就是Exception,如果沒有這句話,下麵的getMessage不能調用 this: Exception => def log(): Unit = { // 既然我就是Exception, 那麼就可以調用其中的方法 println(getMessage) } } //class Console extends Logger {} //對嗎? 錯誤 //class Console extends Exception with Logger {}//對嗎? 正確
8.5 嵌套類
8.5.1 嵌套類的使用1
8.5.2 Scala嵌套類的使用2
編寫程式,在內部類中訪問外部類的屬性
-方式1
內部類如果想要訪問外部類的屬性,可以通過外部類對象訪問
即訪問形式:外部類名.this.屬性名
案例演示
//外部類 //內部類訪問外部類的屬性的方法1 外部類名.this.屬性 class ScalaOuterClass { //定義兩個屬性 var name = "Jack" private var sal = 199.6 class ScalaInnerClass { //成員內部類, def info() = { // 訪問方式:外部類名.this.屬性名 // 怎麼理解 ScalaOuterClass.this 就相當於是 ScalaOuterClass 這個外部類的一個實例, // 然後通過 ScalaOuterClass.this 實例對象去訪問 name 屬性 // 只是這種寫法比較特別,學習java的同學可能更容易理解 ScalaOuterClass.class 的寫法. println("name = " + ScalaOuterClass.this.name + " sal =" + ScalaOuterClass.this.sal) } } }
-方式2
內部類如果想要訪問外部類的屬性,也可以通過外部類別名訪問(推薦)
即訪問方式:外部類名別名.屬性名
案例演示
object boke_demo01 { def main(args: Array[String]): Unit = { //測試1. 創建了兩個外部類的實例 val outer1: ScalaOuterClass = new ScalaOuterClass(); val outer2: ScalaOuterClass = new ScalaOuterClass(); //在scala中,創建成員內部類的語法是 //對象.內部類 的方式創建, 這裡語法可以看出在scala中,預設情況下內部類實例和外部對象關聯 val inner1 = new outer1.ScalaInnerClass val inner2 = new outer2.ScalaInnerClass //測試一下使用inner1 去調用 info() inner1.info() //這裡我們去調用test inner1.test(inner1) //在預設情況下,scala的內部類的實例和創建該內部類實例的外部對象關聯. // inner1.test(inner2) inner2.test(inner2) //創建靜態內部類實例 val staticInner = new ScalaOuterClass.ScalaStaticInnerClass() } } //外部類 //內部類訪問外部類的屬性的方法2 使用別名的方式 //1. 將外部類屬性,寫在別名後面 class ScalaOuterClass { myouter => //這裡我們可以這裡理解 外部類的別名 看做是外部類的一個實例 class ScalaInnerClass { //成員內部類, def info() = { // 訪問方式:外部類別名.屬性名 // 只是這種寫法比較特別,學習java的同學可能更容易理解 ScalaOuterClass.class 的寫法. println("name~ = " + myouter.name + " sal~ =" + myouter.sal) } } //定義兩個屬性 var name = "Jack" private var sal = 999.9 } object ScalaOuterClass { //伴生對象 class ScalaStaticInnerClass { //靜態內部類 } }
8.5.3 類型投影
-案例演示
//外部類 //內部類訪問外部類的屬性的方法2 使用別名的方式 //1. 將外部類屬性,寫在別名後面 class ScalaOuterClass { myouter => //這裡我們可以這裡理解 外部類的別名 看做是外部類的一個實例 class ScalaInnerClass { //成員內部類, def info() = { // 訪問方式:外部類別名.屬性名 // 只是這種寫法比較特別,學習java的同學可能更容易理解 ScalaOuterClass.class 的寫法. println("name~ = " + myouter.name + " sal~ =" + myouter.sal) } //這裡有一個方法,可以接受ScalaInnerClass實例 //下麵的 ScalaOuterClass#ScalaInnerClass 類型投影的作用就是屏蔽 外部對象對內部類對象的 //影響 def test(ic: ScalaOuterClass#ScalaInnerClass): Unit = { System.out.println("使用了類型投影" + ic) } } //定義兩個屬性 var name = "Jack" private var sal = 999.9 }
-解決方式-類型投影
類型投影是指:在方法聲明上,如果使用 外部類#內部類 的方式,表示忽略內部類的對象關係,等同於Java中內部類的語法操作,我們將這種方法稱之為 類型投影(即:忽略對象的創建方式,只考慮類型)