① 伴生對象和伴生類
①-① 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代碼
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ā)父類.