8. Scala面向對象編程(高級部分)

8.1 靜態屬性和靜態方法

  8.1.1 靜態屬性-提出問題 

      有一羣小孩在玩堆雪人,不時有新的小孩加入,請問如何知道如今共有多少人在玩?請使用面向對象的思想,編寫程序解決java

      小孩堆雪人

  8.1.2 基本介紹

      -Scala中靜態的概念-伴生對象mysql

        Scala語言是徹底面向對象(萬物皆對象)的語言,因此並無靜態的操做(即在Scala中沒有靜態的概念)。可是爲了可以和Java語言交互(由於Java中有靜態概念),就產生了一種特殊的對象來模擬類對象,咱們稱之爲類的伴生對象。這個類的全部靜態內容均可以放置在它的伴生對象中聲明和調用sql

  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) 伴生對象對應的類稱之爲伴生類,伴生對象的名稱應該和伴生類名一致oracle

      3) 伴生對象中的屬性和方法均可以經過伴生對象名(類名)直接調用訪問app

      4) 從語法角度來說,所謂的伴生對象其實就是類的靜態方法和成員的集合ide

      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)的示意圖

      Scala繼承特質示意圖

  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特質使用

      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中內部類的語法操做,咱們將這種方法稱之爲 類型投影(即:忽略對象的建立方式,只考慮類型)

相關文章
相關標籤/搜索