• Scala零基础教学【81-89】


    第81讲:Scala中List的构造是的类型约束逆变、协变、下界详解

    首先复习四个概念——协变、逆变、上界、下界

    对于一个带类型参数的类型,比如 List[T]:

    如果对A及其子类型B,满足 List[B]也符合 List[A]的子类型,那么就称为covariance(协变);

    如果 List[A]是 List[B]的子类型,即与原来的父子关系正相反,则称为contravariance(逆变)。

    协变:

    ____              _____________ 
    |     |             |             |
    |  A  |             |  List[ A ]  |
    |_____|             |_____________|
       ^                       ^ 
       |                       | 
     _____               _____________ 
    |     |             |             |
    |  B  |             |  List[ B ]  |
    |_____|             |_____________|
    

    逆变:

    ____              _____________ 
    |     |             |             |
    |  A  |             |  List[ B ]  |
    |_____|             |_____________|
       ^                       ^ 
       |                       | 
     _____               _____________ 
    |     |             |             |
    |  B  |             |  List[ A ]  |
    |_____|             |_____________|
    

    在声明Scala的泛型类型时,“+”表示协变,而“-”表示逆变。

    • C[+T]:如果A是B的子类,那么C[A]是C[B]的子类。
    • C[-T]:如果A是B的子类,那么C[B]是C[A]的子类。
    • C[T]:无论A和B是什么关系,C[A]和C[B]没有从属关系。

     

    根据Liskov替换原则,如果A是B的子类,那么能适用于B的所有操作,都适用于A。让我们看看这边Function1的定义,是否满足这样的条件。假设Bird是Animal的子类,那么看看下面两个函数之间是什么关系:

    def f1(x: Bird): Animal // instance of Function1[Bird, Animal]
    def f2(x: Animal): Bird // instance of Function1[Animal, Bird]

    在这里f2的类型是f1的类型的子类。为什么?

    我们先看一下参数类型,根据Liskov替换原则,f1能够接受的参数,f2也能接受。在这里f1接受的Bird类型,f2显然可以接受,因为Bird对象可以被当做其父类Animal的对象来使用。

    再看返回类型,f1的返回值可以被当做Animal的实例使用,f2的返回值可以被当做Bird的实例使用,当然也可以被当做Animal的实例使用。

    所以我们说,函数的参数类型是逆变的,而函数的返回类型是协变的。

    那么我们在定义Scala类的时候,是不是可以随便指定泛型类型为协变或者逆变呢?答案是否定的。通过上面的例子可以看出,如果将Function1的参数类型定义为协变,或者返回类型定义为逆变,都会违反Liskov替换原则,因此,Scala规定,协变类型只能作为方法的返回类型,而逆变类型只能作为方法的参数类型。类比函数的行为,结合Liskov替换原则,就能发现这样的规定是非常合理的。

    总结:参数是逆变的或者不变的,返回值是协变的或者不变的。

    (1) U >: T

    这是类型下界的定义,也就是U必须是类型T的父类(或本身,自己也可以认为是自己的父类)。

    (2) S <: T

    这是类型上界的定义,也就是S必须是类型T的子类(或本身,自己也可以认为是自己的子类)。

    /**
      * Scala中List的构造是的类型约束逆变、协变、下界详解
      */
    abstract class Big_Data
    class Hadoop extends Big_Data
    class Spark extends Big_Data
    
    object List_Constructor_Internals {
    
      def main(args: Array[String]){
        /**
          *   def ::[B >: A] (x: B): List[B] =
          *        new scala.collection.immutable.::(x, this)
          *   泛型[B >: A]可以看出B是A的上界,而::方法的返回值是List[B],这就说明::方法的返回值是返回上界的类型的值。
          *
          **/
        val hadoop = new Hadoop :: Nil
        //List[hadoop]
        val big_Data = new Spark :: hadoop
        //List[Big_Data]
        
      }
    
    }
    

    第82讲:Scala中List的ListBuffer是如何实现高效的遍历计算的?

    object ListBuffer_Internals {
    
      def main(args: Array[String]) {
        val list = List(1,2,3,4,5,6,7,8,9)
        println(increment(list))
        println(increment_MoreEffective(list))
        println(increment_MostEffective(list))
      }
      //递归方式处理
      def increment(list: List[Int]): List[Int] = list match {
          case List() => List()
          case head :: tail => head + 1 :: increment(tail)
        }
      //赋值新的list,循环加1
      def increment_MoreEffective(list: List[Int]): List[Int] = {
        var result = List[Int]()
        for(element <- list) result = result ::: List(element +1)
        result
      }
      //做一个新的ListBuffer,遍历添加
      def increment_MostEffective(list: List[Int]): List[Int] = {
        import scala.collection.mutable.ListBuffer
        var buffer = new ListBuffer[Int]
    	for(element <- list) buffer += element + 1
    	buffer.toList
      }
    
    }
    

    第83讲:Scala中List的实现内幕源码揭秘

     ListBuffer(链表缓存)相当于List的一个工具类,List本身继承ListBuffer,拥有ListBuffer中的非私有的方法。对List的操作其实有部分是通过ListBuffer完成的。

    exported为ListBuffer中的flag(default:false),当flag为true时,表明Buffer已进行对toList操作,此时再进行连接等操作时,会有copy链表的动作发生,消耗内存,在实际编程中应谨慎。

      override def take(n: Int): List[A] = {
        val b = new ListBuffer[A]
        var i = 0
        var these = this
        while (!these.isEmpty && i < n) {
          i += 1
          b += these.head
          these = these.tail
        }
        if (these.isEmpty) this
        else b.toList
      }
    

     注意最后利用b.toList 耗时与长度没有关系。只返回start部分,所有toList是一个高效的方法。

      override def toList: List[A] = {
        exported = !start.isEmpty
        start
      }
    

    List的子类::[B] 

    final case class ::[B](private var hd: B, private[scala] var tl: List[B]) extends List[B] {}


     

    第84讲:Scala中List和ListBuffer设计实现思考

    思考:Scala List内部有很多操作都是ListBuffer做的,因为改变元素,ListBuffer非常高效。

    我们看到tl是var类型,但是他属于scala包以及子包,我们看上去可变的,但是由于包的限制我们看不到。

    list列表追加元素,如果tl前面没有private[scala],可以改变除了第一个元素,其他所有元素构建list,

    因为我们有同样的tl,追加不同的元素,构造不同的列表,可以共享case class::,

    操作尾部的列表,指向同样的数据结构。

    如果完全对外开放的,由于tl元素不可控,对于共享就很麻烦。

    ListBuffer有start,last0,把元素内容作为start,从后边追加。

    我们既要保证元素的高效性,又要外部是 函数式风格,所以用private[scala] var

    listbuffer是scala子包的内容,所以可以访问和修改list,而外部是private的,其他的对象不可以修改list

    这样就保证了既能可变,又有函数式风格。

    第85讲:Scala中For表达式的强大表现力实战 

    case class Person(name: String, isMale: Boolean, children: Person*)
    object For_Expressive {
    
      def main(args: Array[String]) {
        val lauren = Person("Lauren", false)
        val rocky = Person("Rocky", true)
        val vivian = Person("Vivian", false, lauren, rocky)
        val persons = List(lauren, rocky, vivian)
    
        val result = persons filter (person => !person.isMale) flatMap(
          person => {
            println(person.children);
            println("###"+person)
            person.children map { child => println("###"+child);(person.name, child.name) }
          }
          )
    
        println("******")
        println(result)
        
        val forResult = for (person <- persons; if !person.isMale; child <- person.children) 
          yield (person.name, child.name)
        println(forResult)
        
      }
    
    }
    

    第86讲:Scala中For表达式的生成器、定义和过滤器 

    第87讲:Scala中使用For表达式做查询

    case class Book(title : String , authors : List[String])
    object For_Query {
    
      def main(args: Array[String]) {
        val books: List[Book] = List(
        Book("Structure and Interpretation ", List("Abelson , Harold", "Sussman")),
        Book("Principles of Compiler Design",
          List("Aho, Alfred", "Ullman, Jeffrey")),
        Book("Programming in Modula-2", List("Wirth, Niklaus")),
        Book("Introduction to Functional Programming", List("Bird, Richard")),
        Book("The Java Language Specification",
          List("Gosling, James", "Joy, Bill", "Steele, Guy", "Bracha, Gilad")))
        
    //    val result = for(b <- books ; a <- b.authors if a startsWith "Gosling") yield b.title
        val result = for(b <- books if (b.title indexOf "Programming") >= 0 ) yield b.title
        println(result)
      }
    
    }
    

    第88讲:Scala中使用For表达式实现map、flatMap、filter

      def main(args: Array[String]) {}
      
      def map[A, B](list: List[A], f: A => B): List[B] =
    		  for(element <- list) yield f(element)
      def flatMap[A, B](list: List[A], f: A => List[B]): List[B] =
    		  for(x <- list; y <- f(x)) yield y
      def filter[A](list: List[A], f: A => Boolean): List[A] =
    		  for(elem <- list if f(elem)) yield elem
    }
    

    第89讲:Scala中使用For表达式实现内幕思考

  • 相关阅读:
    linux学习 建立静态库,动态库,写简单的makefile
    C++中顶层const和底层const
    BDB (Berkeley DB)数据库简单介绍(转载)
    Java中Map的使用
    Spring MVC 3 深入总结
    nvl,空时的推断和取值
    java堆栈 (转)
    mybatis--面向接口编程
    HDU 4888
    socket编程——一个简单的样例
  • 原文地址:https://www.cnblogs.com/sunrunzhi/p/9884657.html
Copyright © 2020-2023  润新知