① 特質(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 ();
}
}
ImplicitDemo04ForImplicitClass2
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ā)