scala-19 高級類型

  • 單例類型( singleton type )可用於方法串接和帶對象參數的方法
  • 類型投影( type projection )對所有外部類的對象都包含了其內部類的實例
  • 類型別名( type alias )給類型指定一個短小的名稱
  • 結構類型( structural type )等效於“鴨子類型( duck typing )”
  • 存在類型( existential type )爲泛型類型的通配參數提供了統一形式
  • 使用自身類型( self type )來表明某特質對混入它的類或對象的類型要求
  • “蛋糕模式 ( cake pa憂em )”用自身類型來實現依賴注入
  • 抽象類型( abstract type )必須在子類中被具體化
  • 高等類型( higher-kinded type )帶有本身爲參數化類型的類型參數

單例類型

  • 給定任何引用v ,你可以得到類型v.type ,它有兩個可能的值: v和null 。 這昕上
    去像是一個挺古怪的類型,但它在有些時候很有用
  • 返回this的方法,可以把調用串接起來,例如 book.setAuthor(“Cay Fostman”).setTitle(“Quick Scala”)
class Document{
  private var title:String ="Null"
  private var author:String ="Null"
  def setTitle(title:String):Document={
    this.title=title
    this
  }
  def setAuthor(author:String):Document={
    this.author = author
    this
  }

  override def toString: String = s"title:${title},author:${author}"
}

val book = new Document
book.setAuthor("Cay Fostman").setTitle("Quick Scala")
  • 如果有個子類的話,由於 setTitle返回的是this, Scala將返回類型推斷爲Document 。 但 Document並沒有addChapter方法 。
class Book extends Document{
  private var chapter = "Null"
  def addChapter(chp:String):Book={
    this.chapter=chp
    this
  }

  override def toString: String = super.toString + s",chpter:${chapter}"
}
  • 解決方法是聲明setTitle的返回類型爲this.type ,在父類的方法的返回類型設置爲this.type
class Document{
  private var title:String ="Null"
  private var author:String ="Null"
  def setTitle(title:String):this.type ={
    this.title=title
    this
  }
  def setAuthor(author:String):this.type ={
    this.author = author
    this
  }

  override def toString: String = s"title:${title},author:${author}"
}
class Book extends Document{
  private var chapter = "Null"
  def addChapter(chp:String):Book={
    this.chapter=chp
    this
  }

  override def toString: String = super.toString + s",chpter:${chapter}"
}

//  使用
val b = new Book
b.setAuthor("fdf").setTitle("f").addChapter("df")
  • 如果你想要定義一個接受object實例作爲參數的方法,可以使用單例類型 ,對象.type,不能直接寫Title,這樣是對象,不是類型
object Title
class Document {
  private var useNextArgAs: Any= null
  def set(obj : Title.type ): this.type= { useNextArgAs =obj ; this }
  def to(arg : String) =if (useNextArgAs ==Title) title= arg ; else
}
//使用
val book = new Document
book.set(Title).to ("Scala for the Impatient")

類型投影

class Network{
  class Member(val name:String){
    val contacts=new ArrayBuffer[Network#Member]
  }
}

路徑

  • 表達式com.horstmann. impatient.chatter.Member
    或者,如果我們將Member嵌套在伴生對象當中的話,com.horstmann.impatient.Network.Member,這樣的表達式稱爲路徑
  • 在最後的類型之前,路徑的所有組成部分都必須是“穩定的”,也就是說,它必須指定到單個、有窮的範圍 。 組成部分必須是以下當中的一種 :
    – 包
    – 對象
    – val
    – this,super,super[S],C.this,C.super,C.super[S]
  • 路徑的組成部分不能是類,嵌套的內部類不是單個類型,而是每個實例都有各自的一套類型,爺爺不能是var類型的
  • 在內部,編譯器將所有嵌套的類型表達式a.b.c.T都翻譯成類型投影a.b.c.type#T。舉例來說,chatter.Member就成爲chatter.type#Member任何位於chatter.type單例中的Member。這不是你通常需要擔心的問題。不過,有時候你會看到關於類型a.b.c.type#T的報錯信息。將
    它翻譯回a.b.c.T即可

類型別名

  • 對於複雜類型,可以用type關鍵字創建一個簡單的別名
object Book1{
  type Index1 = scala.collection.mutable.ArrayBuffer[Int]
}

val hm:Index1 = new Book1.Index1()
hm.append(10)
println(hm)

type i = Int
val a:i =10 
  • 類型別名必須被嵌套在類或對象中 。 它不能出現在 Sca la文件的頂層
  • type關鍵字同樣被用於那些在子類中被具體化的抽象類型
abstract class Reader {
  type Contents
  def read(fileName: String): Contents
}

結構類型

  • 結構類型指的是一組關於抽象方法 、字段和類型的規格說明,這些抽象方法、字段和類型是滿足該規格的類型必須具備的
def appendLines (target: { def append(str : String): Any },
                 lines: Iterable [String] ) {
  for (l <- lines) {
    target.append(l); target.append("\n")
  }
}
  • 你可以對任何具備append方法的類的實例調用appendLines方法 。 這比定義一個
    Appendable特質更爲靈活,因爲你可能並不總是能夠將該特質添加到使用的類上 。在背後, Scala使用反射來調用 target,append(…)。 結構類型讓你可以安全而方便地做這樣的反射調用
  • 相比常規方法調用,反射調用的開銷要大得多 。 因此,你應該只在需要抓住那些無法共享一個特質的類的共通行爲的時候才使用結構類型
  • 結構類型與諸如JavaScript或Ruby這樣的動態類型編程語言中的“鴨子類型”很相似。在這些語言當 中,變量沒有類型。當你寫下obj . quack()的時候 , 運行時會去檢查obj 指向 的特定對象在那一刻是否具備quack方法。換句話說 ,你無須將obj 聲明爲 Duck (鴨子),只要它走起路來以及嘎嘎叫起來像一隻鴨子即可。

複合類型

  • 複合類型的定義形式如下:T1 with T2 with T3 …
    其中,T1 T2 T3 等是類型。要想成爲該複合類型的實例,某個值必須滿足每一個類型的要求才行。因此,這樣的類型也被稱作交集類型。
    你可以用複合類型來操縱那些必須提供多個特質的值。例如:
    val image=newArrayBuffer[java.awt.Shape with java.io.Serializable] 只能添加那些既是形狀( Shape )也是可被序列化的對象
  • 你可以把結構類型的聲明添加到簡單類型或複合類型 。 例如:
    Shape with Serializable { def contains(p : Point): Boolean )。該類型的實例必須既是Shape 的子類型也是Serializable的子類型,並且必須有一個帶Point參數的contain s方法

從技術上講,如下結構類型
{def append(str: String): Any }
是如下代碼的簡寫 :
AnyRef { def append(str : String) : Any}
而複合類型
Shape with Serializable
是以下代碼的簡寫 :
Shape with Serializable {)

中置類型

  • 中置類型是一個帶有兩個類型參數的類型,以“中置”語法表示,類型名稱寫在兩個類型參數之間
    type ×[A,B] = (A,B)
    val ab :String × Int = ("sky",4)
  • 所有中置類型操作符都擁有相同的優先級 。 和常規操作符一樣,它們是左結合的一一除非它們的名稱以:結尾
  • 中置類型的名稱可以是任何操作符字符的序列(除單個*外)。這個規則是爲了避免與變長參數聲明T*混淆。

存在類型

  • 存在類型的定義方式是在類型表達式之後跟上 forSome { … },花括號中包含了 type和val的聲明 。例如:Array [T] forSome ( type T < : JComponent }
    上述代碼和你在第 18章中看到的類型通配符效果是一樣的:
    Array [_ <: JComponent] 。
  • Scala的類型通配符只不過是存在類型 的“語法糖” 。 例如:
    Array[ _ ]等同於Array[T] forSome { type T }
    而Map[_,_]等同於Map[T, U] forSome { type T; type U }
  • 更復雜一點的Map[T , U] forSome { type T; type U <: T }
  • 該方法將會接受相同網絡的成員,但拒絕那些來自不同網絡的成員
    def process[M <: n.Member forSome { val n: Network }] (m1: M, m2: M)= (m1, m2)
    在這裏插入圖片描述
  • 要不帶警告地使用存在類型,你必須引人 scala.language.existentials

Scala類型系統

在這裏插入圖片描述
在這裏插入圖片描述

自身類型

  • 特質可以要求混入它的類擴展自另一個類型 。 你用自身類型( self type )的聲明來定義特質:this: 類型A =>這樣的特質只能被混入給定類型A的子類當中
trait Logged {
  def log(msg: String)
} 
trait LoggedException extends Logged {
  this: Exception =>
  def log() { log(getMessage()) }
  // OK to call getMessage because this is an Exception
}
  • 多個類型要求
this: T with U with ... =>
  • 自身類型並不會自動繼承,需要重複聲明自身類型
trait ManagedException extends LoggedException {
	this: Exception =>
	...
}

依賴注入

抽象類型

  • 類或特質可以定義一個在子類中實現的抽象類型
  • 在特質Reader中,抽象類型Contents
import java.awt.image.BufferedImage
import java.io.File
import javax.imageio.ImageIO
import scala.io.Source

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).mkString
  }
}
class ImageReader extends Reader{
  override type Contents = BufferedImage

  override def read(fileName: String): Contents = {
    ImageIO.read(new File(fileName))
  }
}
  • 同樣的效果可以通過類型參數實現
trait Reader[C]{
  def read(fileName:String):C
}
class StringReader extends Reader[String]{
  override def read(fileName: String): String = Source.fromFile(fileName).mkString
}
class ImageReader extends Reader[BufferedImage]{
  override def read(fileName: String): BufferedImage = ImageIO.read(new File(fileName))
}
  • 如果類型是在類被實例化時給出的,則使用類型參數 。如果類型是在子類中給出的,則使用抽象類型。
  • 在構建子類時給出類型參數並沒有什麼不好。但是當有多個類型依賴時,抽象類型用起來更方便,可以避免使用一長串類型參數。
  • 抽象類型還能夠描述類型間那些微妙的相互依賴。抽象類型可以有類型界定,這就和類型參數一樣,例如type Event <: EventObject

家族多態

高等類型

  • 這兩個有點難理解,後面有機會再理解一下
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章