• 快学scala


    快学scala

    标签(空格分隔): scala


    第五章 类

    5.1 类中属性的定义

    1. 属性的声明:
      scala类的属性有4种方法定义:var , val , private var , 同类对象私有字段private[this] var

      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调用

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

    5.2 类的构造器

    1. 主副构造器
      (1)如果一个类没有显示的声明主构造器,则会自动加入无参构造
      (2)辅助构造器名称为this(避免修改类名时要修改多个辅助构造器的名称)'
      (3)辅助构造器的开头必须以主构造器开始
      (4)主构造器中的字段会自动被解析成类中的属性

      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方法,可以在模式匹配中用于属性匹配

      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")
      
        val s1 = new Student("lj",26,"09101306")
      
        p("lj") = 27 // update function
      }
      

    5.3 枚举类型

    1. 声明枚举类型
      (1)object继承Enumeration
      (2)枚举的属性调用Value方法
      (3)枚举的name自动设置为属性名
      object Color extends Enumeration{
        val Red = Value
        val Yellow = Value
      }
      
      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> 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> 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
    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
    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)加到另一个网络中

      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

      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("
    ")
        }
      }
    

    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,  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
    
  • 相关阅读:
    中国大概可用NTPserver地址
    ROOT android 原则。 基于(zergRush)
    Struts2他们拦截器实例定义—登陆权限验证
    引用与指针
    address_space 从哪里来
    C++ 可以多个函数声明
    linux下远程管理利器-tmux
    内核空间和用户空间的分界 PAGE_OFFSET
    io端口
    C中程序的内存分配
  • 原文地址:https://www.cnblogs.com/moonlord/p/6672409.html
Copyright © 2020-2023  润新知