Scala(六)-①-面相對象高級-特質(zhì)(下)-嵌套類-隱式轉(zhuǎn)換和隱式參數(shù)-隱式類

① 特質(zhì)(下)

在上一篇博文中,我已經(jīng)介紹了Scala中靜態(tài)屬性和方法之伴生對象實現(xiàn),以及特質(zhì)入門的一部分內(nèi)容.該篇博文我將會介紹特質(zhì)(下)、嵌套類、隱式(上).對于特質(zhì)(下).我主要介紹以下主題, 疊加特質(zhì)自身類型、嵌套類

Why

疊加特質(zhì)

為什么我們要學(xué)習(xí)疊加特質(zhì),這是因為Scala中并無多重繼承,因為多重繼承會帶來很多問題, 為了能夠?qū)崿F(xiàn)多重繼承又能規(guī)避其問題, Scala使得特質(zhì)能夠疊加,通俗說是讓一個類能夠具備多個特質(zhì).

自身類型(強制規(guī)定能夠混入特質(zhì)的類)

自身類型主要是為了解決特質(zhì)的循環(huán)引入,即因為混入特質(zhì)而形成的依賴回環(huán).該技術(shù)
通過再特質(zhì)中明確規(guī)定哪些類能夠混入該特質(zhì)來解決這個問題.

How

疊加特質(zhì)

規(guī)則和語法
  • 特質(zhì)執(zhí)行順序由右向左.先執(zhí)行File的insert方法,File中的super表示Trait1,所以會再執(zhí)行Trait1的insert方法.
// 創(chuàng)建一個SomeObject對象,混入Trait1和Trait2特質(zhì)
val obj = new SomeObject with Trait1 with Trait2
疊加特質(zhì)Demo

代碼

object TraitDemo05MultiplyMixin {
  def main(args: Array[String]): Unit = {

    val mysql = new MySQL with DB with File
    mysql.insert(100)
  }
}

class MySQL {}


trait Operator {

  def insert(id :Int)
}

trait Data extends Operator {
  override def insert(id: Int): Unit = {
    println("插入數(shù)據(jù)")
  }
}

trait DB extends Data {
  override def insert(id: Int): Unit = {
    println("向數(shù)據(jù)庫插入數(shù)據(jù)")
    super.insert(id)
  }
}

trait File extends Data {
  override def insert(id: Int): Unit = {
    println("向文件中插入數(shù)據(jù)")
    super.insert(id)
  }
}

輸出

向文件中插入數(shù)據(jù)
向數(shù)據(jù)庫插入數(shù)據(jù)
插入數(shù)據(jù)
  • 特質(zhì)構(gòu)造由左到右,由父到子.由左到右構(gòu)造特質(zhì),并且先構(gòu)造父特質(zhì)再構(gòu)造子特質(zhì),如果父特質(zhì)之前構(gòu)造過則不再構(gòu)造
    順序
object TraitDemo05MultiplyMixin02 {
  def main(args: Array[String]): Unit = {

    val mysql = new MySQL05 with DB05 with File05
  }
}

class MySQL05 {}


trait Operator05 {
  println("Operator")
}

trait Data05 extends Operator05 {
  println("Data")
}

trait DB05 extends Data05 {
  println("DB")
}

trait File05 extends Data05 {
  println("File")
}

輸出

Operator
Data
DB
File

自身類型

代碼

object TraitDemo09SelfTypeForCycleRefenrece {

  def main(args: Array[String]): Unit = {
//      val oracle = new Oracle09 with Logger // 錯誤
    var mysql = new MySQL09 with Logger
  }

}

trait Logger {
  // 聲明混入該特質(zhì)的類必須是Exception或者其子類
  this:Exception=>
  def log(): Unit = {
    getMessage
  }
}

class Oracle09 {}

class MySQL09 extends Exception {

}

What

疊加特質(zhì)

  • 對象中混入特質(zhì)原理見上篇.構(gòu)建順序和調(diào)用順序原理就不再做細究.

Details

疊加特質(zhì)

  • 特質(zhì)聲明順序左到右,構(gòu)建順序也是.
  • Scala在執(zhí)行疊加對象的方法時,會首先從后面的特質(zhì)(從右向左)開始執(zhí)行
  • Scala中特質(zhì)中如果調(diào)用super,并不是表示調(diào)用父特質(zhì)的方法,而是向前面(左邊)繼續(xù)查找特質(zhì),如果找不到,才會去父特質(zhì)查找
  • 如果想要調(diào)用具體特質(zhì)的方法,可以指定:super[特質(zhì)].xxx(…).其中的泛型必須是該特質(zhì)的直接超類類型
  • 富接口的概念: 具有普通方法抽象方法的特質(zhì)的稱呼
  • 特質(zhì)中如果有抽象成員,無論字段或方法都需要混入的類來實現(xiàn).
object TraitDemo06RichInterface {

  def main(args: Array[String]): Unit = {
    val mySQL = new MySQL06 with DBDriver06 {
      // 實現(xiàn)抽象字段
      override var numberOfThread: Int = _

      // 實現(xiàn)抽象方法
      override def delete: Unit = {

      }
    }

  }
}

class MySQL06 { }

trait DBDriver06 {

  var numberOfThread : Int
  private var operatorType = "insert"

  def insert(): Unit = {

  }

  def delete
}
  • 特質(zhì)構(gòu)造順序之聲明時混入.聲明時混入先構(gòu)造子類,再從左到右夠著特質(zhì)(并且每構(gòu)造特質(zhì)之時先構(gòu)造父特質(zhì)),最后構(gòu)造本類.

特質(zhì)構(gòu)造分兩種,一種聲明時混入,一種動態(tài)混入(new的時候混入).兩種唯一的區(qū)別是,動態(tài)混入先構(gòu)造本類,而聲明時混入最后才構(gòu)造本類,其它都一樣.

代碼

object TraitDemo07DecalreMixin {
  def main(args: Array[String]): Unit = {
    println("聲明時混入")
    
    val ff = new FF

    println("動態(tài)混入")
    // 動態(tài)混入
    val ee = new EE with CC with DD


  }
}

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...")
}

// 聲明時混入ee、cc、dd
class FF extends EE with CC with DD {
  println("F....")
}

// 聲明時混入ee
class KK extends EE {
  println("K....")
}

輸出

聲明時混入
E...
A...
B....
C....
D....
F....
動態(tài)混入
E...
A...
B....
C....
D....
  • 擴展特質(zhì): 特質(zhì)能夠繼承類,達到擴展特質(zhì)的功能.混入擴展特質(zhì)的類要和其有相同的父類,不然會導(dǎo)致多重繼承的錯誤.
object TraitDemo08ExtendTrait {
  def main(args: Array[String]): Unit = {
    val log = new Log4Scala

  }
}


trait Log extends Exception {

  def log: Unit = {
    getMessage
  }
}
// 如果去掉LogModule繼承Exception,則運行時會報錯,提示多重繼承
class LogModule extends Exception{

}

class Log4Scala extends LogModule with Log {

}

② 嵌套類

Java中內(nèi)部類

嵌套類對應(yīng)Java的內(nèi)部類,所以我們先來看看Java中的內(nèi)部類.在Java中,一個類總共有五大>成員.分別是

  • 屬性
  • 方法
  • 內(nèi)部類
  • 構(gòu)造器
  • 代碼塊

Java中內(nèi)部類
在Java中一個類里面可以再嵌套一個類,嵌套在里面的類被稱為內(nèi)部類,在外面的類稱為

外部類.

Why

為什么Java中有內(nèi)部類,為了解決什么問題?Java中的內(nèi)部類主要為了解決不同類之前無法訪>問其私有成員的問題.內(nèi)部類就可以訪問外部的私有成員.

How
class OuterClass {
  class InnserClass { // 成員內(nèi)部類,位于成員位置且不為靜態(tài)
  
  }
  
  static class StaticInnerClass {  // 靜態(tài)內(nèi)部類, 位于成員位置,且為靜態(tài)
  
  }
  
  public void test() {
       class InnerClass02 { // 局部內(nèi)部類· 位于方法,有類名
       
       }
       
       new Thread() {  // 匿名內(nèi)部類
           @Override
           public void run() {
               super.run();
           }
       }.start();
  }
}


Why

為什么Scala中也要有嵌套類?

Scala中也有內(nèi)部類的概念, Scala中又稱為嵌套類.也就是說嵌套是為支持Java中的內(nèi)部類概念.

How

語法和規(guī)則
  • Scala中成員內(nèi)部類寫在伴生類中,靜態(tài)內(nèi)部類寫在伴生對象中(因為只有伴生對象才能模擬靜態(tài))
  • Scala中的成員內(nèi)部類從屬于外部類對象.所以同一個外部類的不同實例,其對應(yīng)的內(nèi)部類類型不一樣.
  • Scala還可以通過給外部類起別名,內(nèi)部類利用別名訪問外部類成員
/**
  * @author sweetcs
  */
object InnerClassDemo01ForCreate {
  def main(args: Array[String]): Unit = {

    val outer01 = new ScalaOuterClass01
    val outer02 = new ScalaOuterClass01

    val inner01 = new outer01.ScalaInnerClass01
    val inner02 = new outer02.ScalaInnerClass01
    val inner03 = new ScalaOuterClass01.ScalaStaticInnerClass01

    inner01.showInfo(inner01)
    inner01.showInfoWithAlia()
//    inner01.showInfo(inner02) // 類型不匹配

  }
}

/**
  * Scala的成員內(nèi)部類創(chuàng)建方式 `new 外部類實例.內(nèi)部類`.Scala的成員內(nèi)部類類型和外部類實例關(guān)聯(lián)(Scala內(nèi)部類是從屬于外部類對象的).這兩點都和Java的成員內(nèi)部類有區(qū)別
  */

object ScalaOuterClass01 {

  // 靜態(tài)內(nèi)部類
  class ScalaStaticInnerClass01 {

    def showInfo(): Unit = {

      println("ScalaStaticInnerClass01")
    }
  }

}

class ScalaOuterClass01 {
  // 給外部類起別名為outer
  outer=>
  var name = "scott"
  private var age = 11
  // 成員內(nèi)部類
  class ScalaInnerClass01 {

    def showInfo(inner: ScalaInnerClass01): Unit = {
      println(s"name=${ScalaOuterClass01.this.name} age = ${ScalaOuterClass01.this.age}")
    }

    def showInfoWithAlia(): Unit = {
      println(s"name=${outer.name} age = ${outer.age}")
    }
  }
}    
  • Scala中當(dāng)要屏蔽外部對象,用外部類#內(nèi)部類來屏蔽
    //下面的 ScalaOuterClass#ScalaInnerClass 類型投影的作用就是屏蔽 外部對象對內(nèi)部類對象的影響
    def test(ic: ScalaOuterClass#ScalaInnerClass): Unit = {
      System.out.println("使用了類型投影" + ic)
    }

③ 隱式轉(zhuǎn)換

① Why

為什么Scala中需要隱式轉(zhuǎn)換和隱式參數(shù)
  • 其一.隱式轉(zhuǎn)換自動將高精度類型自動轉(zhuǎn)換為低精度類型.我們在程序中,經(jīng)常會遇到類試將 高精度類型轉(zhuǎn)換為低精度類型, 這時候就得用強制類型轉(zhuǎn)換.但是如果程序中很多地方都要用到,就會導(dǎo)致到處都充斥著強制類型轉(zhuǎn)換代碼.Scala為了解決 高精度類型自動轉(zhuǎn)換為低精度類型于是引入了隱式轉(zhuǎn)換技術(shù),該技術(shù)主要依托于隱式函數(shù)

  • 其二.隱式轉(zhuǎn)換動態(tài)增加類庫功能.在程序開發(fā)中我們需要不斷迭代軟件,給程序加入新的功能.如果加入新的功能就會需要改變源代碼, 比如需要改變某一個類中的代碼,這違背了OPC原則(open close prionceple,修改代碼被關(guān)閉,增加功能被開發(fā)).

  • 隱式參數(shù)主要是為了讓多個函數(shù)中的參數(shù)能夠同時具備同一個默認值.

  • 無論一、二兩點都是依托于隱式調(diào)用做的自動類型轉(zhuǎn)換.

② How

語法和規(guī)則
  • 隱式函數(shù)需要用implicit聲明
implicit def functionName(形參名:轉(zhuǎn)換類型) : 目標類型 = { 
    // 轉(zhuǎn)換邏輯
}
  • 隱式參數(shù)需要配合隱式變量,其語法為
implicit name :類型 = xxx
implicit def functionName(implicit 形參名:轉(zhuǎn)換類型) : 目標類型 = { 
    // 轉(zhuǎn)換邏輯
}
隱式轉(zhuǎn)換emo
  • 高精度自動轉(zhuǎn)低精度
/**
  * @author sweetcs
  */
object ImplicitDemo01 {

  def main(args: Array[String]): Unit = {


    implicit def f1(num:Double) :Int = { // 底層生成了f1$1
      num.toInt
    }

    val num : Int = 3.5 // 底層編譯f1$1(3.5)
    println(s"num=${num}")
  }
}
  • 隱式轉(zhuǎn)換動態(tài)增加類庫功能.隱式轉(zhuǎn)換技術(shù)還可以用來為類添加方法,實現(xiàn)動態(tài)增加一個類的功能.(不同對象之間的自動轉(zhuǎn)換,調(diào)用對象自動轉(zhuǎn)被調(diào)用對象)
/**
  * @author sweetcs
  */
object ImplicitDemo02 {
  def main(args: Array[String]): Unit = {

    implicit def addDelete(mysql :MySQL) : DB = {
      new DB
    }

    val mysql = new MySQL
    mysql.insert()
    mysql.delete()
  }
}

class MySQL {

  def insert(): Unit = {
    println("Mysql inserting")
  }
}

class DB {

  def delete(): Unit = {
    println("DB deleting")
  }
}
隱式參數(shù)DEMO
/**
  * @author sweetcs
  */
object ImplicitDemo03ForImplicitParamter {
  def main(args: Array[String]): Unit = {

      implicit var city :String = "beijing"

      implicit def queryDetailInfo(implicit city :String): Unit = {
        println(city)
      }
      queryDetailInfo
  }
}

③ What

隱式函數(shù)底層實現(xiàn)

如口類-ImplicitDemo01

public final class ImplicitDemo01
{
  public static void main(String[] paramArrayOfString)
  {
    ImplicitDemo01$.MODULE$.main(paramArrayOfString);
  }
}

實際入口類-ImplicitDemo01$

public final class ImplicitDemo01$
{
  public static final  MODULE$;
  
  private final int f1$1(double num)
  {
    return (int)num;
  }
  
  public void main(String[] args)
  {
    int num = f1$1(3.5D);
    Predef..MODULE$.println(new StringContext(Predef..MODULE$.wrapRefArray((Object[])new String[] { "num=", "" })).s(Predef..MODULE$.genericWrapArray(new Object[] { BoxesRunTime.boxToInteger(num) })));
  }
  
  private ImplicitDemo01$()
  {
    MODULE$ = this;
  }
  
  static
  {
    new ();
  }

④ Details

  • 隱式轉(zhuǎn)換函數(shù)的函數(shù)名可以是任意的,隱式轉(zhuǎn)換與函數(shù)名稱無關(guān),只與函數(shù)簽名(函數(shù)參數(shù)類型和返回值類型)有關(guān)。
  • 隱式函數(shù)可以有多個(即:隱式函數(shù)列表),但是需要保證在當(dāng)前環(huán)境下,只有一個隱式函數(shù)能被識別
implicit def a(d: Double) = d.toInt
implicit def b(d: Double) = d.toInt
val i: Int = 3.5 // 編譯期會不知道要調(diào)用哪個,因為具有二義性
  • 當(dāng)一個隱式參數(shù)匹配不到隱式值(匹配原則就是按類型匹配),才會使用默認值
  • 優(yōu)先級式 傳值>大于隱式值>默認值.

④ 隱式類

Why

隱式類

How

  • 其所帶的構(gòu)造參數(shù)有且只有一個
  • 隱式類必須被定義在或者伴生對象包對象中.即隱式類不能是頂級.
  • 隱式類不能是case class
  • 作用域內(nèi)不能有與之相同名稱的標識符

Demo

  • 隱式類
/**
  * @author sweetcs
  */
object ImplicitDemo04ForImplicitClass {

  def main(args: Array[String]): Unit = {
    // 在隱式類的作用域內(nèi)創(chuàng)建MySQL04類的對象,該隱式類自動轉(zhuǎn)換就會生效
    implicit class DB04(mysql :MySQL04) {
        def insert(): Unit = {
          println("插入數(shù)據(jù)")
        }
    }

    val mysql = new MySQL04
    mysql.getConnection()
    
    mysql.insert()  // 返回了一個ImplicitDemo04ForImplicitClass$DB04$2實例,并調(diào)用其insert方法

  }
}

class MySQL04 {

  def getConnection(): Unit = {
    println("獲取數(shù)據(jù)庫連接")
  }

}

What

隱式類底層是如何轉(zhuǎn)換成的?
隱式類-程序入口類-ImplicitDemo04ForImplicitClass

public final class ImplicitDemo04ForImplicitClass
{
  public static void main(String[] paramArrayOfString)
  {
    ImplicitDemo04ForImplicitClass$.MODULE$.main(paramArrayOfString);
  }
}

ImplicitDemo04ForImplicitClass$類


public final class ImplicitDemo04ForImplicitClass$
{
  public static final  MODULE$;
  
  private final ImplicitDemo04ForImplicitClass$DB04$2 DB04$1(MySQL04 mysql)
  {
    return new ImplicitDemo04ForImplicitClass$DB04$2(mysql);
  }
  
  public void main(String[] args)
  {
    MySQL04 mysql = new MySQL04();
    mysql.getConnection();
    DB04$1(mysql).insert();
  }
  
  private ImplicitDemo04ForImplicitClass$()
  {
    MODULE$ = this;
  }
  
  static
  {
    new ();
  }
}

ImplicitDemo04ForImplicitClassDB042

public class ImplicitDemo04ForImplicitClass$DB04$2
{
  public void insert()
  {
    Predef..MODULE$.println("插入數(shù)據(jù)");
  }
  
  public ImplicitDemo04ForImplicitClass$DB04$2(MySQL04 mysql) {}
}

Details

  • 隱式類必須被定義在“類”或“伴生對象”或“包對象”里,即隱式類不能是頂級的
  • 作用域內(nèi)不能有與之相同名稱的標示符

隱式轉(zhuǎn)換時機總結(jié)

  • 當(dāng)方法中的參數(shù)類型和目標參宿好類型不一致時會觸發(fā)
  • 當(dāng)對象調(diào)用的方法或者成員變量不存在時,會觸發(fā)
最后編輯于
?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

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