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

  • 伴生對象必須和伴生類同名.如果只有伴生對象,其會自動(dòng)生成一個(gè)對應(yīng)的空的半生類,如果只有伴生類其不會自動(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中會多出哪個(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),則子類依舊會繼承父類實(shí)現(xiàn)的接口方法
  • 如果使用mixin(動(dòng)態(tài)混入),則子類不會繼承父類實(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)建出來的類不會影響其子類?

如下代碼是動(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è)抽象類中,并且會將其變成一個(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).自然其子類也就不會有對應(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)系作者
  • 序言:七十年代末泡躯,一起剝皮案震驚了整個(gè)濱河市贮竟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌精续,老刑警劉巖坝锰,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粹懒,死亡現(xiàn)場離奇詭異重付,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)凫乖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門确垫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弓颈,“玉大人,你說我怎么就攤上這事删掀∠杓剑” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵披泪,是天一觀的道長纤子。 經(jīng)常有香客問我,道長款票,這世上最難降的妖魔是什么控硼? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮艾少,結(jié)果婚禮上卡乾,老公的妹妹穿的比我還像新娘。我一直安慰自己缚够,他們只是感情好幔妨,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著谍椅,像睡著了一般误堡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上毯辅,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天埂伦,我揣著相機(jī)與錄音,去河邊找鬼思恐。 笑死沾谜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的胀莹。 我是一名探鬼主播基跑,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼描焰!你這毒婦竟也來了媳否?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤荆秦,失蹤者是張志新(化名)和其女友劉穎篱竭,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體步绸,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掺逼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瓤介。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吕喘。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赘那,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出氯质,到底是詐尸還是另有隱情募舟,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布闻察,位于F島的核電站拱礁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏辕漂。R本人自食惡果不足惜觅彰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钮热。 院中可真熱鬧填抬,春花似錦、人聲如沸隧期。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仆潮。三九已至宏蛉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間性置,已是汗流浹背拾并。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鹏浅,地道東北人嗅义。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像隐砸,于是被迫代替她去往敵國和親之碗。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容