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表达式实现内幕思考
Scala零基础教学【81-89】的更多相关文章
- Scala零基础教学【1-20】
基于王家林老师的Spark教程——共计111讲的<Scala零基础教学> 计划在9月24日内完成(中秋节假期之内) 目前18号初步学习到25讲,平均每天大约完成15讲,望各位监督. 初步计 ...
- Scala零基础教学【102-111】Akka 实战-深入解析
第102讲:通过案例解析Akka中的Actor运行机制以及Actor的生命周期 Actor是构建akka程序的核心基石,akka中actor提供了构建可伸缩的,容错的,分布式的应用程序的基本抽象, a ...
- Scala零基础教学【90-101】Akka 实战-代码实现
第90讲:基于Scala的Actor之上的分布式并发消息驱动框架Akka初体验 akka在业界使用非常广泛 spark背后就是由akka驱动的 要写消息驱动的编程模型都首推akka 下面将用30讲讲解 ...
- Scala零基础教学【61-80】
第61讲:Scala中隐式参数与隐式转换的联合使用实战详解及其在Spark中的应用源码解析 第62讲:Scala中上下文界定内幕中的隐式参数与隐式参数的实战详解及其在Spark中的应用源码解析 /** ...
- Scala零基础教学【41-60】
第41讲:List继承体系实现内幕和方法操作源码揭秘 def main(args: Array[String]) { /** * List继承体系实现内幕和方法操作源码揭秘 * * List本身是一个 ...
- Scala零基础教学【21-40】
第24讲:Scala中SAM转换实战详解 SAM:single abstract method 单个抽象方法 我们想传入一个函数来指明另一个函数具体化的工作细节,但是重复的样板代码很多. 我们不关 ...
- Android零基础入门第89节:Fragment回退栈及弹出方法
在上一期分享的文章末尾留了一个课后作业,有去思考如何解决吗?如果已经会了那么恭喜你,如果还不会也没关系,本期一起来学习. 一.回退栈 在前面两期的示例中,当我们完成一些操作后,如果想要回到操作之前的状 ...
- Scala实战高手****第2课:Scala零基础实战入门的第一堂课及如何成为Scala高手
val声明的不可变的战略意义:1.函数式编程中要求值不可变,val天然符合这一特性:2.在分布式系统中,一般都要求值不可变,这样才能够要求分布式系统的设计和实现,同时拥有更高的效率,val声明的内容都 ...
- Android零基础入门第81节:Activity数据传递
在Android开发中,经常要在Activity之间传递数据.前面也学习了Activity和Intent相关基础,接下来一起来学习Activity的数据传递. 一.简介 通过前面的学习知道,Inten ...
随机推荐
- 【uva10829-求形如UVU的串的个数】后缀数组+rmq or 直接for水过
题意:UVU形式的串的个数,V的长度规定,U要一样,位置不同即为不同字串 https://uva.onlinejudge.org/index.php?option=com_onlinejudge&am ...
- CVE-2016-6662 mysql RCE测试
参考:http://bobao.360.cn/learning/detail/3027.html ,我尝试第一种方法 1.先修改mysql_hookandroot_lib.c里面的反弹地址和端口: # ...
- Linux调试介绍
1. 介绍 本文介绍了调试的一些常用函数和工具 2. 函数 用户态函数: backtrace()/backtrace_symbols() 内核态函数: dump_stack() 3. 工具 工具: g ...
- kuangbin 带你飞 概率期望
正推不行就逆推! 经典问题:生日悖论 换成其互斥事件:m个人, 每个人生日都不相同的概率 ≤ 0.5 时最小人数. 这就是邮票收集问题的变形:每个邮票至少出现一次的概率 小于等于 0.5 邮票收集问题 ...
- SuSE Linux10.1 网络设置以及和主机通信end
设置步骤如下: 1.首先判断VMware Bridge Protocol协议是否已经安装.在本地连接的属性中可以查看. 2.虚拟机设置为bridged 3. 虚拟机-Edit-Virtual Netw ...
- hibernate基础学习
转载自:http://blog.csdn.net/fb281906011/article/details/17628111 一:下载hibernate:http://hibernate.org/orm ...
- [ Python - 10 ] 练习:批量管理主机工具
需求: 主机分组 登录后显示主机分组,选择分组后查看主机列表 可批量执行命令.发送文件,结果实时返回 主机用户名密码可以不同 流程图: 说明: ## 需求: 主机分组 登录后显示主机分组,选择分组后查 ...
- Delphi2007新功能 -- 有限的栈对象
今天使用Delphi2007,一个误输入,无意中发现Delphi2007的record类型居然能够和TObject一样定义方法和属性,而且不需要调用类似TObject.Create方法就能生成一个re ...
- Python Standard Library
Python Standard Library "We'd like to pretend that 'Fredrik' is a role, but even hundreds of vo ...
- AutoIt 3.0 操作之初体验(第一个脚本hello world)
AutoIt 目前最新是v3 版本,它是一个使用类似BASIC 脚本语言的免费软件,它被设计用来进行Windows GUI(图形用户界面)的自动化测试.它利用模拟键盘按键,鼠标移动和窗口/控件的组合来 ...