快學scala

快學scala

標籤(空格分隔): scalajava


第五章 類

5.1 類中屬性的定義

  1. 屬性的聲明:
    scala類的屬性有4種方法定義:var , val , private var , 同類對象私有字段private[this] varpython

    class Person{
    
      var varage = 0     // 類中全部var產生公有的setter和getter
      val valage = -1    // 類中全部val產生公有的getter
    
      private var age1 = 0    // 產生私有的setter和getter
      private[this] var age2 = 3    // 不產生setter,getter
    
      // 經過複寫setter和getter
      def age = age2    // getter
      def age_=(newAge:Int) = {    // setter  : field_=
        this.age2 = newAge
      }
    }
  2. scala的setter和getter調用es6

    val p = new Person
      p.varage_=(2)   // setter
      p.age           // getter

5.2 類的構造器

  1. 主副構造器
    (1)若是一個類沒有顯示的聲明主構造器,則會自動加入無參構造
    (2)輔助構造器名稱爲this(避免修改類名時要修改多個輔助構造器的名稱)'
    (3)輔助構造器的開頭必須以主構造器開始
    (4)主構造器中的字段會自動被解析成類中的屬性api

    class Person(var name:String,var age:Int){
      def this(name:String){
        this(name,-1)
      }
      def update(name:String,age:Int) = {
        print("update function is called")
        this.age = age
      }
    }
  2. 伴生對象
    (1)伴生對象適用於既有實例方法,又有靜態方法的時候
    (2)伴生對象中的apply方法能夠用來不帶new產生對象,apply的方法體要調用伴生類的主/輔助構造器方法
    (3)伴生對象中的unapply方法,能夠在模式匹配中用於屬性匹配
    ```scala
    object Person{
    def apply(name: String): Person = new Person(name)
    def unapply(arg: Person): Option[Int] = Option(arg.age)
    }緩存

    // 繼承的寫法
    class Student(name:String,age:Int,val sid:String) extends Person(name){
    }網絡

    object Main extends App{
    val p = Person("lj") // 利用伴生對象的apply方法產生對象
    p match { // 模式匹配至關於手動調用了下面的unapply方法
    case Person(-1) => println("match success")
    }
    if (Person.unapply(p).get == -1)
    println("unapply match success")app

    val s1 = new Student("lj",26,"09101306")ide

    p("lj") = 27 // update function
    }
    ```函數

5.3 枚舉類型

  1. 聲明枚舉類型
    (1)object繼承Enumeration
    (2)枚舉的屬性調用Value方法
    (3)枚舉的name自動設置爲屬性名
    ```scala
    object Color extends Enumeration{
    val Red = Value
    val Yellow = Value
    }this

    object Test1 extends App{
    println(Color.Yellow.toString) // Yellow
    println(Color.Yellow.id) // 1
    }

    ```

5.4 Option類

  1. Option的子類有Some和None。
  2. 經過Some的構造器,講一個cal轉換爲Option類型的字段
def getOptval(aaa:Person):Option[Person] = Some(aaa)
print(getOptval(new Person(1,2)).get) //Person@7921b0a2

第十三章 集合

13.1 scala的集合繼承層級

(1)全部集合繼承自Iterable特質,所以,訪問全部集合的通用代碼爲:

val coll = ... // 某種集合
val iter = coll.iterator
while(iter.hasnext)
    iter.next

(2)scala的集合大體分爲3類:
      i) Seq:按照插入順序排序的序列
      ii) Set:每次插入一個元素,都會根據某種經排序方法決定元素在集合中所處的位置。set中沒有重複的元素
      iii) Map:鍵值對對偶

13.2 Seq類型的集合

一. Seq的繼承層級

  1. 不可變序列:1.png-62.4kB
    (1)Vector是ArrayBuffer的不可變版本,它擁有下標,以樹型結構存儲節點,支持快速的隨機訪問。每一個節點最多可存放32個子節點。所以,對於一個100萬個元素的向量,只須要四層節點(\(10^6 \approx32^4\)),訪問任意一個元素,最多隻須要4眺
    (2)Range是一個整數序列,好比1,2,3,4,5,6,7,8,9 它不存儲全部元素,只存儲起始值,結束值和增值

  2. 可變序列:
    bb.png-56.3kB

二. 列表

  1. scala中的列表要麼是Nil(空表),要麼是一個head元素加上一個tail(列表)。如下列表的聲明等價
    ```scala
    scala> 9::4::2::Nil
    res5: List[Int] = List(9, 4, 2)

    scala> List(9,4,2)
    res6: List[Int] = List(9, 4, 2)

    scala> 9::List(4,2)
    res7: List[Int] = List(9, 4, 2)

    ```
  2. 集合操做
    (1)向後追加元素(:+),向前追加元素(+:) (只用於向插入順序有關的集合中追加)

    scala> val v1 = Vector(1,2,3,4)
    scala> v1 :+ 8
    res27: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3, 4, 8)
    scala> 9 +: v1
    res28: scala.collection.immutable.Vector[Int] = Vector(9, 1, 2, 3, 4)

    (2)+操做符,向Set等與插入順序無關的集合中追加元素
    scala scala> val s1 = Set(1,2,3,4) scala> s1 +9 res31: scala.collection.immutable.Set[Int] = Set(1, 9, 2, 3, 4)
    (3)++--分別爲向集合中追加多個元素

三. 化簡,摺疊,掃描

  1. reduceLeftreduceRight
    (1)coll.reduceLeft(op)表達式,將op函數相繼應用到集合中的元素,如圖造成一個樹形結構
    (2)reduceLeft是從集合的左端開始,reduceRight是從集合的右端開始

    scala> List(1,7,2,9).reduceLeft(_-_)    // 1-7-2-9
    scala> List(1,7,2,9).reduceRight(_-_)   // 1-(7-(2-9))
    res31: Int = -13
  2. foldLeftfoldRight
    (1)摺疊方法讓調用者能夠自定義集合計算的初始元素,進行樹型結構的計算
    scala scala> List(1,7,2,9).foldRight(5)(_-_) // 1-(7-(2-(9-5))) = -8 scala> List(1,7,2,9).foldLeft(5)(_-_) // 5-1-7-2-9 res33: Int = -14
    (2)foldLeftfoldRight的簡寫形式:/::\

    scala> (5 /: List(1,2,3))(_-_)    // 5-1-2-3
    res35: Int = -1
    (3)利用摺疊方法計算詞頻
    初始化一個空Map[Char,Int],每一步將頻率映射和新遇到的字母結合在一塊兒
    scala scala> (Map[Char,Int]() /: "Mississippi"){(m,c) => m + (c -> (m.getOrElse(c,0)+1))} res43: scala.collection.mutable.Map[Char,Int] = Map(M -> 1, s -> 4, p -> 2, i -> 4)
  3. scanLeftscanRight將摺疊和映射組合在一塊兒,得出每一次計算的中間結果

    scala> List(1,2,3,4).scanLeft(0)(_-_)
    res46: List[Int] = List(0, -1, -3, -6, -10)

13.3 流

  1. 定義:
    (1)Stream是一個尾部被懶計算的不可變列表,經過操做符#::能夠構造一個流
    (2)Strea的尾部懶計算後會緩存起來

    scala> val tenMore = numsFrom(10)
    tenMore: Stream[BigInt] = Stream(10, ?)
    
    scala> tenMore.tail.tail
    res51: scala.collection.immutable.Stream[BigInt] = Stream(12, ?)
    
    res52: Stream[BigInt] = Stream(10, 11, 12, ?)     // 10,11,12已通過緩存

    (3)用take得到多個答案,而後用force強制求值
    scala scala> tenMore.take(5).force res54: scala.collection.immutable.Stream[BigInt] = Stream(10, 11, 12, 13, 14)
    (4)不要不經take直接調用force,不然會一次計算出stream的全部尾值,知道內存溢出

13.4 懶試圖

  1. 全部的集合都能經過view方法轉換爲懶計算的視圖,視圖不一樣於流,連第一個元素也不去計算

    scala> (0 to 5)
    res55: scala.collection.immutable.Range.Inclusive = Range(0, 1, 2, 3, 4, 5)
    
    scala> (0 to 5).view           // 視圖
    res56: scala.collection.SeqView[Int,scala.collection.immutable.IndexedSeq[Int]] = SeqView(...)
    
    scala> (0 to 5).view.map(_*2)  // SeqView(....):全部元素全不計算
    res57: scala.collection.SeqView[Int,Seq[_]] = SeqViewM(...)
    
    scala> (0 to 5).view.map(_*2).force
    res58: Seq[Int] = Vector(0, 2, 4, 6, 8, 10)

第十五章 高級類型

本章要點:

  • 列表項

18.1 單例類型

1. 定義:

對於任意一個引用對象v,能夠獲得一個類型v.type。這個類型有兩個可能的值:v(對象自己,但以類型的形式出現) 和 null

2. 用法:

對於那種返回this的方法,經過this.type把這些方法串接起來

(1)錯誤寫法:Document類的setter方法最後返回了this,而該方法的返回值類型若是直接寫成Document,雖然能夠串聯調用setTile和setAuthor,可是一旦出現Document的子類Book,則Book產生的對象調用setatile後返回的類型被寫成Document,也就不能串聯調用setATitle和setAuthor

// 錯誤示例
class Document {
  def setTitle(title: String):Document = {
    println("set title:" + title)
    this
  }
  def setAuthor(author:String):Documente = {
    println("set author:" + author)
    this
  }
}

val doc = new Document()
doc.setAuthor("lj").setTitle("scala Education")  // setAuthor返回的Document類型,能夠串聯調用setTitle

class Book extends Document{
  def addChgapter(chapter:String) :Book = {
    println("add chapter:" + chapter)
    this
  }
}

val book = new Book()
book.setTitle("another book").addChgapter("1 chapter")   // 編譯報錯,由於setTitle返回的類型是Document,而Document類沒有addChapter方法

(2)正確寫法:爲了使繼承Document的Book類的對象也能串聯調用,能夠改造這些setter方法的返回值爲this.type,這樣,Book類的對象book在調用setTitle方法時,返回的類型就是book.type,而因爲book對象有一個addChapter方法,所以能夠串接起來

class Document {
  def setTitle(title: String):this.type = {
    println("set title:" + title)
    this
  }
  def setAuthor(author:String):this.type = {
    println("set author:" + author)
    this
  }
}

class Book extends Document{
  def addChgapter(chapter:String) :this.type = {
    println("add chapter:" + chapter)
    this
  }
}

val book = new Book()
book.setTitle("another book").addChgapter("1 chapter")

(3)其次,若是想要定義一個接收object實例做爲參數的方法,也可使用單例類型。那麼爲何對於單例對象的方法不直接調用,還要傳進一個object對象做爲參數在調用呢?由於有人喜歡構造那種調用起來像是一句話的代碼
book set Title to "Scala for the impatient"

object Title

class Document {
  private var useNextArgAs:Any = null
  // 用Title.type聲明傳入的是Title單例對象, 用this.type聲明返回值使得集成類的setter方法也能串聯調用
  def set(obj:Title.type):this.type = {
    useNextArgAs = obj
    this
  }

  def to(arg:String) : Unit={
    if (this.useNextArgAs==Title)
      println("set finish")
    else
      ""

  }
}

object Test extends App{
  val doc = new Document()
  doc set Title to "scala for the impatient"   // 構造英文語句
}

18.2 類型投影

一. 內部類的細粒度控制

  1. scala中,嵌套類屬於它包含的外部對象,即每一個實例都有本身的內部類
    以下,chatter.member和myFace..member是不一樣的類。不能講任何一個網絡(NetWork)的成員(Member)加到另外一個網絡中
    ```scala
    class NetWork{
    class Member(val name:String) {
    val contacts = new ArrayBuffer[Member] // 這個泛型Member,指的是[對象.Member]
    }

    private val members = new ArrayBuffer[Member]

    def join(name:String) :Member= {
    val m = new Member(name)
    members += m
    m
    }
    }

    val chatter = new NetWork
    val myFace = new NetWork

    val Fred = chatter.join("Fred")
    val Barney = myFace.join("Barney")

    Fred.contacts += Barney
    ```

  2. 內部類從屬於每一個對象這種約束是默認存在的,若是不想要這種約束,應該把Member類挪到NetWork類的外面。更好的選擇是在Network的伴生對象中。若是想使用更爲鬆散的定義,能夠用類型投影NetWork#Member,表示任何Network的Member
    scala class NetWork{ class Member(val name:String) { val contacts = new ArrayBuffer[NetWork#Member] //val contacts = new ArrayBuffer[Member] // 這個泛型Member,指的是[對象.Member] } ... }

18.3 類型別名

  1. 對於複雜類型,eg:HashMap[String,(Int,Int)],可使用type關鍵字建立一個簡單別名,eg:index
  2. 類型別名必須嵌套在類或對象中,他不能出如今scala文件的頂層
class Book{
  import scala.collection.mutable._
  type index = mutable.HashMap[String,(Int,Int)]
}

18.5 結構類型

1. 定義

結構類型是一組關於抽象方法,字段和類型的規格說明,這些抽象方法,字段,類型是該規格類型必須具有的。
寫法上:用大括號包圍這些抽象方法,字段,類型

2. 能夠用結構類型聲明函數的形參類型

下例所示,appendlines方法的形參是任何具備append方法的對象和一個string泛型的iterater,appendlines會調用這個對象的append方法

def appendLines(target: {def append(str:String): Any},lines:Iterable[String]): Unit ={
    for(l <- lines){
      target.append(l);target.append("\n")
    }
  }

3. 結構類型也稱做鴨子類型

鴨子類型就像python這種動態類型語言,變量沒有類型,當你寫下obj.quack()時,運行時回去檢查obj指向的對象在那一刻是否具備quack方法。換句話說:你不須要把obj聲明爲Duck類型,只要它運行時有Duck的方法(走起來,叫起來像鴨子同樣)

18.6 符合類型

1. 定義:

符合類型的定義形式以下: \(T_1\) with \(T_2\) with \(T_3\) ...,表示要成爲該複合類型的實例,必須知足每個類型的要求(好比實現了這幾個特質的方法),所以,符合類型也稱做交集類型

val images = new ArrayBuffer[java.awt.Shape with java.io.Serializable]
val rect = new Rectangle(5,10,20,30)     // Rectangle extends Rectangle2D  implements Shape,java.io.Serializable
images += rect

2. 符合類型的後面能夠加上結構類型

new ArrayBuffer[java.awt.Shape with java.io.Serializable {def setBounds(x: Int, y: Int, width: Int, height: Int):Unit}]

表示這個ArrayBuffer裏的對象既要知足Shape和Serializable接口,還要存在setBounds方法

18.7 中置類型的寫法

1. 寫法:

(1)scala提供了一種讓類型的描述趨於數學中置表達式形式的寫法:用中置表達式組合多個泛型。eg:用String Map Int來表示Map[String,Int]
(2)寫法:泛型1 類 泛型2
(3)中置表達式也能夠用來模式匹配,eg:

case class Person[S,T](val name:S,val age:T)

val p : String Person Int= Person("搖擺少年夢",19)

p match {
    case "搖擺少年夢" Person 18=> println("matching is ok")
    case name Person age=> println("name:"+name+"  age="+age)
}

18.8 存在類型

1. 定義

(1)scala的存在類型是爲了與java的類型統配符兼容
(2)寫法:在類型表達式後面跟上forSome{},裏面包含了type和val的聲明,這些聲明是對被forSome修飾的類型作一個限制。

2. type限制

下例中,t1的存在類型和t2的類型通配符是等價的,類型通配符是存在類型的語法糖

type t1 = Array[T] forSome { type T<:JComponent}
type t2 = Array[_<:JComponent]

// 存在類型容許使用更復雜的類型關係
type t3 = Map[T,U] forSome {type T,type U<:T}

3. val限制

有的嵌套類經過類型投影NetWork#Member,擴大了泛型範圍。但一些方法又想把嵌套類侷限於每一個對象的嵌套類,就是用存在類型加以限定

val chatter = new NetWork
val myFace = new NetWork

val Fred = chatter.join("Fred")    // 同一個網絡下的成員
val Fred2 = chatter.join("Fred2")  // 同一個網絡下的成員
val Barney = myFace.join("Barney") // 不一樣網絡

Fred.contacts += Barney

def process[M <: n.Member forSome { val n:NetWork }](m1:M,m2:M) = (m1,m2)
process(Fred,Fred2)  //process(Barney,Fred2)  => 錯誤:process方法經過forSome裏面的val n:NetWork限制了n.Member爲對象自身的嵌套類,使得方法接收相同網絡的成員,拒毫不同網絡的成員

18.9 自身類型

1. 定義

(1)自身類型是對特質自身的一種限制,它指出該特質只能被混入哪一個類中,或智能被混入哪一個類的子類中
(2)形式:this: 類型 =>

trait Logged{
  def log(msg:String)
}

trait LoggerException extends Logged{
  this:Exception =>    // 這個this指代混入特質後的對象
    def log(){
      log(getMessage())     // getMessage方法來自於this,而this又是Exception的子類
    }
}

object Test extends App{
  type f = JFrame with LoggerException   // 定義類型時不報錯,建立對象時會由於LoggerException的自身類型限制而報錯
  // val v1 = new f   // 報錯:f類型是JFrame混入LoggerException,而JFrame不是Exception的子類
}

2. 繼承帶有自身類型特質的特質

(1)若是自帶有自身類型限制的特質被另外一個特質集成,則子特質必須重複寫出自身類型,表示本身和父特質同樣也有混入的限制

trait ManagedException extends LoggerException{
  this:ArrayIndexOutOfBoundsException =>   // 這裏的類型限制要定義成Exception或其子類
    def say(){print("aaa")}
}

18.11 依賴注入

1. 場景

(1)設想一個應用,他須要日誌和驗證功能,固然,驗證功能也須要用到日誌。
(2)設計:所以,能夠把日誌和驗證設爲2個特質Logger和Auth,這兩個特質分別有本身不一樣的實現。而真正的應用類App,只要混入這些不一樣的實現組合,就能使得App擁有日誌和驗證功能。而Auth須要用到Logger,因此在Auth特質中,經過自身類型調用Logger類型的方法

trait Logger{
  def log(msg:String)
}

trait Auth{
  this:Logger =>    // 自身類型調用Logger特質的方法
    def login(id:String,passwd:String):Boolean
}

trait FileLogger extends Logger{
  override def log(msg: String): Unit = {
    println(msg)
  }
}

trait MockAuth extends Auth{
  this:FileLogger =>
    override def login(id: String, passwd: String): Boolean = {
      if(id.equals("guest")) {
        log("guest login fail..")
        false
      }else
        true
    }
}

object App extends FileLogger with MockAuth{    // 此處經過依賴注入變換實現
  def main(args: Array[String]): Unit = {
    login("guest","123456") 
  }
}

(3)這種方法的怪異之處在於,一個App並不是是驗證器和文件日誌器的組合。更天然地表述方式是使用成員變量來實現功能組件,而不是把App經過混入特質變成一個巨大的類型。

trait LogComponent{    // 最外層的大組件

  trait Logger{
    def log(msg:String)
  }

  val logger:Logger   // 抽象變量

  class FileLogger extends Logger{
    override def log(msg: String): Unit = {
      println("write in file: "+msg)
    }
  }
}


trait AuthCompnonent{    // 最外層的大組件
  this:LogComponent =>   // 使用抽象變量logger
  trait Auth{
    def login(id:String,passwd:String):Boolean
  }

  val auth:Auth // 抽象變量

  class MockAuth extends Auth{
    override def login(id: String, passwd: String): Boolean = {
      if (id.equals("guest")){
        logger.log("guest cannot login")  // 這個logger變量來自於LoggerComponent,究竟是哪一個實現取決於繼承的特質
        false
      }else
        true
    }
  }
}

object App extends LogComponent with AuthCompnonent{

  override val logger = new FileLogger    // 成員變量
  override val auth = new MockAuth        // 成員變量

  def main(args: Array[String]): Unit = {
    auth.login("guest","123456")
  }
}

18.12 抽象類型

1. 定義

(1)類或特質中,定義一個在子類中被具體化的抽象類型。eg:以下的Reader特質:

trait Reader{
  type Contents
  def read(fileName:String):Contents
}

class StringReader extends Reader{
  override type Contents = String
  override def read(fileName: String):Contents = Source.fromFile(fileName,"UTF-8").mkString    // mkString method return string, corresponding with TYPE contents
}

class ImageReader extends Reader{
  override type Contents = BufferedImage
  override def read(fileName: String): Contents = ImageIO.read(new File(fileName))
}

(2)固然,在須要子類給出抽象類型的實現這種方法,還能夠經過類型參數實現

trait Reader[C]{
  def read(fileName:String):C
}

class StringReader extends Reader[String]{
  override def read(fileName: String) = Source.fromFile(fileName,"UTF-8").mkString
}

class ImageReader extends Reader[BufferedImage]{
  override def read(fileName: String) = ImageIO.read(new File(fileName))
}

2. 抽象類型和類型參數的好壞對比

(1)若是類型是在建立對象時給出時(不存在繼承該類的子類),就應當使用類型參數。好比構建HashMap[String,Int]
(2)若是類型是在子類中給出,則使用抽象類型。好比上面的Reader就是子類中給出。
(3)固然還有一種狀況是,在子類中給出類型參數。這種方法沒什麼很差,可是一旦父類或父特質中有多個類型參數,子類的定義就會變得冗長。eg:Reader[File,BufferedImage],這樣會使得伸縮性變差

3. 抽象類型能夠有類型界定

trait Listener{
  type Event <: java.util.EventObject
}

trait ActionListener extends Listener{
  type Event = java.awt.ActiveEvent
}

第二十一章

21.1 隱士轉換

1. 定義

(1)隱士轉換:以implicit聲明的帶有單個參數的函數
(2)這個函數自動將一種類型轉換成另外一種類型

case class Fraction(a:Int,b:Int){
  def *(second:Fraction) = Fraction(second.a*a,second.b*b)
}

object Test extends App{
  implicit def int2Fraction(n:Int) = Fraction(n,1)
  val result = 3*Fraction(4,5)    // Int沒有*(Fraction)的方法,但Fraction有*(Fraction)方法
  println(result)
}

2. 利用隱士轉換豐富現有類庫

(1)你多想用new File(README"").read來讀取一個文件,可是jdk的File並未提供這個方法。做爲java,你只能向Oracle公司提交申請,可是scala卻能經過一年隱士轉換來豐富這個api

case class RichFile(filepath:String){
  def read() = Source.fromFile(filepath).mkString("")
}

object Test extends App{
  implicit def file2RichFile(from:File) = new RichFile(from.getAbsolutePath)
  new File("README").read()
}

3.編譯器何時會進行隱士轉換

(1)當表達式所得值的類型,和所處位置的期待類型不同時
(2)當訪問一個不存在的成員或方法時。eg : File.read()。也就是說當調用a.fun(b)時,若是存在2個隱士轉換使得:convert(a)的結果有fun方法,或者存在方法a.fun(convert(b)),則編譯器會使用convert(a)隱士轉換。由於編譯器會把沒有調用成員的對象隱士轉換

4. 編譯器不會進行隱士轉換的狀況

(1)若是在不進行隱士轉換的狀況下能夠經過編譯,則不進行隱士轉換
(2)變量只能通過一次隱士轉換,而不能進行形如convert1(convert2(a))這樣的屢次轉換
(3)若是兩個隱士轉換都知足條件,編譯器報錯。即:convert1(a)b與convert2(a)b都成立

21.2 隱士參數

1. 定義

隱士參數是函數的參數列表中,帶有implicit標記的形參。此時在調用該方法時,編譯器會查詢缺省值

case class Delimiters(left:String,right:String)

object Test extends App{
  def quote(what:String)(implicit delims:Delimiters) = delims.left + what + delims.right
  
  println(quote("impatient scala")(Delimiters("《","》")))   // 顯式調用
  
  implicit val quoteDelimiters = Delimiters("'","'")         // 隱士調用
  println(quote("hello world"))
}

2. 隱士轉換爲隱士參數傳入方法

(1)隱士轉換是一個隱士方法,當方法做爲參數傳入另外一個方法,就造成了含有隱士參數的高階方法

def smaller[T](a:T,b:T)(implicit order: T => Ordered[T]) = {
if(order(a) < b) a else b
}

println(smaller("a","b"))

(2)隱士轉換做爲隱士參數的簡化聲明

def smaller2[T](a:T,b:T)(implicit order: T=>Ordered[T]) = {
if(a<b) a else b       // 隱士轉換自動執行,所以不用顯式調用
}
println(smaller2(2,4))

21.3 類上的隱式泛型:上下文界定

1. 定義

(1)形如T:M的泛型,表示程序的上下文中,存在一個類型爲M[T]的隱式值。一般用於類的泛型限制
(2)類中的方法使用這個上下文界定的隱式值有兩種方法:
        (i.) 經過定義implicit隱士形參
        (ii) 在函數體中使用Predef類的implicitly()方法,傳入上下文界定類型還原出這個隱式值

// method 1
class Pair[T:Ordering](val first:T,val second:T){
  def smaller(implicit ord:Ordering[T])=
    if (ord.compare(first,second)<0) first else second
}

// method 2
class Pair1[T:Ordering](val first:T,val second:T){
  def smaller =
    if( implicitly[Ordering[T]].compare(first,second) < 0 ) first else  second
}

object Test1 extends App{
  println(new Pair1(24,35).smaller)
}

(3)用泛型定義的類,在實例化時,編譯器會經過成員變量推斷出泛型的類型。

21.4 類型證實

1. 定義

(1)類型證實是形如implicit ev T <:< U的一個隱士參數,其中<:<還能夠是<=<,<%<.
(2)三個符號分別表示:T是不是U的子類型,T是否等於U,T是否能夠經過隱士轉換爲U

2. 類型證實是如何實現的

(1)<:<<=<<%<3個符號並不是是語言特性,而是定義在Predef中的三個類
(2)舉例:<:<類的定義
    下面的三行代碼,解釋了scala經過<:<類,實現類型證實的過程。
    (i.) 首先:定義了一個帶有泛型的抽象類<:<,這個類繼承的(From=>To),實際上就是一個Function1(帶有一個形參的函數)
    (ii) 其次:初始化了一個singleton_<:<對象,這個對象的-From+To泛型都是Any,並且複寫了apply方法爲傳入一個Any類型的參數x,而後把x返回出去
    (iii)最後:定義了一個隱士轉換$conforms[A],它的返回值類型爲A <:< A<:<[A,A]的中置寫法)。該方法就是將<:<[Any,Any]強轉爲<:<[A,A](即:Function1[Any,Any]轉換爲Function1[A,A])。而這個泛型A到底可否讓編譯器推斷出來,就是這個類型證實可否經過的關鍵
    (v.)編譯器推斷:因爲<:<類的泛型一個逆變,一個協變。eg:對於<:<[String,AnyRef],編譯器就能推斷出A是String(String逆變成String,AnyRef協變成String)

@implicitNotFound(msg = "Cannot prove that ${From} <:< ${To}.")
sealed abstract class <:<[-From, +To] extends (From => To) with Serializable
private[this] final val singleton_<:< = new <:<[Any,Any] { def apply(x: Any): Any = x }
implicit def $conforms[A]: A <:< A = singleton_<:<.asInstanceOf[A <:< A]

3. 使用舉例

def firstLast[T,IR](it:IR)(implicit env: IR<:<Iterable[T]) = (it.head,it.last)
println(firstLast(List(1,2,3)))

21.5 @implicitNotFound註解

1. 定義

(1)@implicitNotFound註解加載類上,該類須要時隱士轉換函數From->To的To類。意義在於告知編譯器,再不能構建出這個To類時爆出錯誤信息
(2)例如:

@implicitNotFound(msg = "Cannot prove that ${From} <:< ${To}.")
sealed abstract class <:<[-From, +To] extends (From => To) with Serializable
相關文章
相關標籤/搜索