Scala(五)-②-面相對象高級-靜態(tài)屬性和方法、特質(zhì)(上)

① 伴生對象和伴生類

①-① Why

  • Scala語言是完全面相對象的,并不支持靜態(tài)這個(gè)概念,也就沒有靜態(tài)成員(靜態(tài)成員變量和靜態(tài)成員方法).
  • 但是java又支持靜態(tài)這個(gè)概念,有些需求需要用到靜態(tài).所以Scala就使用伴生對象這個(gè)技術(shù)來模擬靜態(tài),使得我們能夠?qū)崿F(xiàn)和靜態(tài)一樣的效果.

①-② How

語法和規(guī)則
  • 伴生對象必須和伴生類同名.
class Person {  // 半生類Person

}

object Person {  // 伴生對象Person.寫在里面的成員可以模擬Java的static效果.

}

  • 可以在伴生對象實(shí)現(xiàn)apply方法.之后直接用類名(apply形參列表)就能夠觸發(fā)apply方法.所以一般創(chuàng)建對象.
object 類名 {
    def apply(形參列表) : 類名 = new 類名(形參列表)
}
Demo
伴生對象模擬靜態(tài)方法
package com.sweetcs.bigdata.scala.day05.chapter08._01_accompobject

/**
  * @author sweetcs
  */
object ChildGameDemo {

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

    val child0 = new Child("lisi")
    val child1 = new Child("zhangsan")
    val child2 = new Child("wangwu")

    Child.count(child0) // 使用類能夠直接調(diào)用伴生對象中的成員
    Child.count(child1)
    Child.count(child2)
    Child.showNumberOfChild()

  }
}

class Child(inName :String) {

  var name :String = inName

}
object Child {

  var numberOfChild : Int = 0

  def count(child: Child): Unit = {
    numberOfChild += 1
  }

  def showNumberOfChild(): Unit = {
    println(s"numberOfChild = $numberOfChild")
  }
}
伴生對象里apply
object ApplyDemo {
  def main(args: Array[String]): Unit = {

    val pig1 = Pig()
    val pig2 = Pig("pei qi")

    println(pig1.name)
    println(pig2.name)

  }
}

class Pig(inName :String) {

  var name :String = inName

  def this() {
    this("")
  }

}

object Pig {

  def apply(inName: String): Pig = new Pig(inName)

  def apply(): Pig = new Pig()

  
}

①-③ What

伴生對象是怎么實(shí)現(xiàn)讓對應(yīng)的伴生類能通過直接調(diào)用 伴生對象里面的成員?

如下分析,先看源代碼對應(yīng)的反編譯的代碼

源代碼
/**
  * @author sweetcs
  */
object AccompObjectDemo {

  def main(args: Array[String]): Unit = {
      PersonOfAccompanyClass.name = "test"
      PersonOfAccompanyClass.show()
  }
}

// 半生類
class PersonOfAccompanyClass {

}

// 半生對象
object PersonOfAccompanyClass {
  var name :String = ""
  def show(): Unit = {
    println(s"name = ${name}")
  }
}
反編譯的AccompObjectDemo和PersonOfAccompanyClass代碼
public final class AccompObjectDemo$
{
  public static final  MODULE$;
  
  static
  {
    new ();
  }
  
  public void main(String[] args)
  {
    PersonOfAccompanyClass..MODULE$.name_$eq("test");
    PersonOfAccompanyClass..MODULE$.show();
  }
  
  private AccompObjectDemo$()
  {
    MODULE$ = this;
  }
}
public final class PersonOfAccompanyClass$
{
  public static final  MODULE$;
  private String name;
  
  public String name()
  {
    return this.name;
  }
  
  public void name_$eq(String x$1)
  {
    this.name = x$1;
  }
  
  public void show()
  {
    Predef..MODULE$.println(new StringContext(Predef..MODULE$.wrapRefArray((Object[])new String[] { "name = ", "" })).s(Predef..MODULE$.genericWrapArray(new Object[] { name() })));
  }
  
  private PersonOfAccompanyClass$()
  {
    MODULE$ = this;this.name = "";
  }
  
  static
  {
    new ();
  }
}

從上面代碼可以看出,底層是通過以下兩句實(shí)現(xiàn)了我們寫的代碼.而這里的MODULE$核心所在,它是一個(gè)靜態(tài)實(shí)例,類型就是PersonOfAccompanyClass$類型.所以底層其實(shí)是創(chuàng)建了一個(gè)對應(yīng)的類名$的類,并將伴生對象中的成員分按訪問控制權(quán)限放入, 并且僅有一個(gè)類名$的類型的實(shí)例(因?yàn)镸ODULE$是靜態(tài)的).

    PersonOfAccompanyClass$.MODULE$.name_$eq("test");
    PersonOfAccompanyClass$.MODULE$.show();

①-④ Details

  • 伴生對象必須和伴生類同名.如果只有伴生對象,其會(huì)自動(dòng)生成一個(gè)對應(yīng)的空的半生類,如果只有伴生類其不會(huì)自動(dòng)生成伴生對象.
  • 如果半生對象中定義一個(gè)apply(apply形參列表),則可以不用new就創(chuàng)建對象,直接通過類名(apply形參列表)即可以觸發(fā)對應(yīng)的apply方法返回對象.

② 特質(zhì)

學(xué)習(xí)特質(zhì)之前我們先來看下Java中的接口.

Java中的接口特點(diǎn)

  • 在Java中, 一個(gè)類可以實(shí)現(xiàn)多個(gè)接口。
  • 在Java中,接口之間支持多繼承
  • 接口中屬性是常量
  • 接口中的方法是抽象的

Java中的接口有以下缺點(diǎn)

  • 如果B實(shí)現(xiàn)了Ia接口,并且C繼承自B,則C中會(huì)多出哪個(gè)被實(shí)現(xiàn)的方法,但是很多時(shí)候我們并不想要.而Scala可以通過mixin動(dòng)態(tài)混入技術(shù)實(shí)現(xiàn)該功能
  • 無法在接口中實(shí)現(xiàn)方法.(在Java8之前), Scala的trait可以實(shí)現(xiàn)該功能.

②-① Why

  • trait擁有接口和抽象類的特點(diǎn).學(xué)習(xí)trait,是因?yàn)閠rait擁有java中接口的特點(diǎn)抽象類的特點(diǎn),其可以很方便的解決以上接口存在的問題.所以我們可以用trait來替代接口.
  • trait可以動(dòng)態(tài)擴(kuò)展類的功能而不通過繼承.trait可以讓一個(gè)類(包含抽象類),能夠不通過繼承trait就可以動(dòng)態(tài)的擴(kuò)展trait里的非抽象方案,增強(qiáng)這個(gè)類的功能.

②-② How

規(guī)則和語法
  • 如果使用傳統(tǒng)方法(extends),則子類依舊會(huì)繼承父類實(shí)現(xiàn)的接口方法
  • 如果使用mixin(動(dòng)態(tài)混入),則子類不會(huì)繼承父類實(shí)現(xiàn)的接口方法
獲取數(shù)據(jù)庫連接(trait的接口特點(diǎn))-傳統(tǒng)方法
object TraitDemo02 {

  def main(args: Array[String]): Unit = {
    val mySQLDriver = new MySQLDriver
    mySQLDriver.getConnection()

    val oracleDriver = new OracleDriver
    oracleDriver.getConnection()

  }
}

trait DBDriver{
  def getConnection()
}

class A {}
class B extends A {}
class MySQLDriver extends A with DBDriver {
  override def getConnection(): Unit = {
      println("連接MySQL成功")
  }
}

class D {}
class OracleDriver extends D with DBDriver {
  override def getConnection(): Unit = {
    println("連接Oracle成功")
  }
}
class F extends D {}

動(dòng)態(tài)混入(mixin)
/**
  * @author sweetcs
  */
object TraitDemo04Mixin {
  def main(args: Array[String]): Unit = {

    val oracle = new OracleWithMixin with DBDriver02
    oracle.insert()
  }
}

class OracleWithMixin {

}

trait DBDriver02 {

  def insert(): Unit = {
    println("正在插入數(shù)據(jù)到數(shù)據(jù)庫中")
  }
}

②-③ What

  • 特質(zhì)中如果還有普通方法,在底層是如何實(shí)現(xiàn)這個(gè)普通方法的調(diào)用呢?其在底層是放在哪個(gè)類里?
反編譯后的代碼
  • Animal.class
public abstract interface Animal
{
  public abstract void sayHi();
  
  public abstract void eat();
}
  • Animal$class.class
public abstract class Animal$class
{
  public static void eat(Animal $this)
  {
    Predef$.MODULE$.println("I am eating~~~");
  }
  
  public static void $init$(Animal $this) {}
}
  • sheep.class
public class Sheep
  implements Animal
{
  public void eat()
  {
    Animal$class.eat(this);
  }
  
  public Sheep()
  {
    Animal$class.$init$(this);
  }
  
  public void sayHi()
  {
    Predef$.MODULE$.println("hello human");
  }
}

可以看到trait中的普通方法其實(shí)真正的實(shí)現(xiàn)是在特質(zhì)名$class這個(gè)類里,并且其將普通方法轉(zhuǎn)換成一個(gè)公共的靜態(tài)方法.Sheep中調(diào)用普通方法,本質(zhì)是是去調(diào)用特質(zhì)名$class.普通方法名()

  • Scala底層是如何實(shí)現(xiàn)Mixin動(dòng)態(tài)混入技術(shù)的,即如何做到不繼承trait而又能用其功能?如何做到使用動(dòng)態(tài)混入創(chuàng)建出來的類不會(huì)影響其子類?

如下代碼是動(dòng)態(tài)混入TraitDemo04Mixin中的反編譯代碼

public final class TraitDemo04Mixin$
{
  public static final  MODULE$;
  
  static
  {
    new ();
  }
  
  public void main(String[] args)
  {
    OracleWithMixin oracle = new OracleWithMixin()
    {
      public void insert()
      {
        DBDriver02$class.insert(this);
      }
    };
    ((DBDriver02)oracle).insert();
  }
  
  private TraitDemo04Mixin$()
  {
    MODULE$ = this;
  }
}
  • DBDriver02$class
public abstract class DBDriver02$class
{
  public static void insert(DBDriver02 $this)
  {
    Predef$.MODULE$.println("正在插入數(shù)據(jù)到數(shù)據(jù)庫");
  }
  
  public static void $init$(DBDriver02 $this) {}
}
  • OracleWithMixin
public class OracleWithMixin {}

分析:

  • 我們知道trait中的普通方法最終在底層是在trait名$calss這個(gè)抽象類中,并且會(huì)將其變成一個(gè)靜態(tài)的方法.這里對應(yīng)的就是DBDriver02$class中的insert公共靜態(tài)方法.
  • TraitDemo04Mixin$是程序真正的main入口,這里可以看到mixin的底層技術(shù)本質(zhì)其實(shí)就是new了一個(gè)匿名子類,并且實(shí)現(xiàn)了對應(yīng)的insert接口,并且在接口中調(diào)用的是DBDriver02$class.insert方法.
  • 因?yàn)槭褂玫氖?code>new 匿名子類,所以這種方式并不是繼承trait(如上OracleWithMixin并沒有繼承DBDriver02).自然其子類也就不會(huì)有對應(yīng)的insert方法.

②-④ Details

  • scala的trait即特質(zhì)需要首字母大寫
  • scala中java中的接口都可以當(dāng)做trait使用.
  • scala中特質(zhì)的使用,用extends關(guān)鍵字,如果有多個(gè)就使用extends 特質(zhì)1 with 特質(zhì)2 with 特質(zhì)3,如果有父類要繼承,那么extends后面發(fā)父類.
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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