scala编程第19章学习笔记(1)——类型参数化
2014-12-31 17:21
381 查看
一、queues函数式队列
函数式队列是一种具有以下三种操作方式的数据结构:
head 返回队列的第一个元素。
tail 返回除第一个元素之外的队列。
实现函数式队列的一种简单方案是以 List作为功能的表达类型。这样,head和tail将转为List的相同操作,而append将变为连结操作。这将得到以下的代码实现:
上面实现的问题在于append操作。它花的时间与存储于队列的元素数量成正比。如果想要的是常量时间的append,可以尝试把表达List里面的元素倒序排列,这样原本最后添加进来的元素现在出现在List的最前面。如下实现:
现在append是常量时间了,但head和tail不是。它们现在所花费的时间与存储在队列中的元素数量成正比。
另一种高效的实现方法:
二、信息隐藏
私有构造器及工厂方法
Java中,可以把构造器声明为私有的使其不可见。scala中,主构造器无须明确定义;不过虽然它的定义隐含于类参数及类方法体中,还是可以通过把private修饰符添加在类参数列表的前边把主构造器隐藏起来,如下所示:
夹在类名与参数之间的private修饰符表明Queue的构造器是私有的:它只能被类本身及伴生对象访问。类名Queue仍然是公开的,因此你可以继续使用这个类,但不能调用它的构造器:
现在客户代码不能再使用Queue类的主构造器,需要有创建新队列的其他方法。一种可能方案是添加辅助构造器,如下:
上面的辅助构造器可以构建空队列。通过改良,它可以带上初始队列元素列表:
其中T*是重复参数的标记。
另一种可能性是添加可以初始元素序列创建队列的工厂方法。比较简洁的做法是定义与类同名的Queue对象及apply方法,如下所示:
注意,工厂方法名为apply,因此客户可以用类似于Queue(1, 2, 3)这样的表达式创建队列。由于Queue是对象而不是函数,这个表达式会被扩展为Queue.apply(1,2,3)。
可选方案:私有类
私有构造器和私有成员是隐藏类的初始化代码和表达代码的一种方式。另一种更为彻底的方式是直接把类本身隐藏掉,仅提供能够暴露类公共接口的特质。
三、变化型注解
上面定义的Queue是特质,不是类型。因为它带有类型参数。结果,将不能创建类型为Queue的变量:
取而代之,特质Queue能够指定参数化的类型,如Queue[String], Queue[Int],或Queue[AnyRef]:
注:根据上面定义的Queue,不同元素类型的队列之间没有子类型关系。Queue[String]对象不能被用作Queue[AnyRef]。
然而,可以用如下的方式改变Queue类定义的第一行,以要求队列协变(弹性)的子类型化:
在正常的类型参数前面加上+号表明这个参数的子类型化是协变(弹性)的。
除了+号之外,还可以前缀加上-号,这表明是需要逆变的(contravariant)子类型化。如果Queue定义如下:
那么如果T是类型S的子类型,这将隐含Queue[S]是Queue[T]的子类型。无论类型参数是协变的,逆变的,还是非协变的,都被称为参数的变化型。可以放在类型参数前的+号和-号被称为变化型注解。
函数式队列是一种具有以下三种操作方式的数据结构:
head 返回队列的第一个元素。
tail 返回除第一个元素之外的队列。
scala> import scala.collection.immutable.Queue import scala.collection.immutable.Queue scala> val q = Queue(1, 2, 3) q: scala.collection.immutable.Queue[Int] = Queue(1, 2, 3)
实现函数式队列的一种简单方案是以 List作为功能的表达类型。这样,head和tail将转为List的相同操作,而append将变为连结操作。这将得到以下的代码实现:
scala> class SlowAppendQueue[T](elems: List[T]) { | def head = elems.head | def tail = new SlowAppendQueue(elems.tail) | def append(x: T) = new SlowAppendQueue(elems ::: List(x)) | } defined class SlowAppendQueue
上面实现的问题在于append操作。它花的时间与存储于队列的元素数量成正比。如果想要的是常量时间的append,可以尝试把表达List里面的元素倒序排列,这样原本最后添加进来的元素现在出现在List的最前面。如下实现:
scala> class SlowHeadQueue[T](smele: List[T]) { | //smele是elems的反转 | def head = smele.last | def tail = new SlowHeadQueue(smele.init) | def append(x: T) = new SlowHeadQueue(x :: smele) | } defined class SlowHeadQueue
现在append是常量时间了,但head和tail不是。它们现在所花费的时间与存储在队列中的元素数量成正比。
另一种高效的实现方法:
scala> class Queue[T]( | private val leading: List[T], private val trailing: List[T] | ) { | private def mirror = | if (leading.isEmpty) | new Queue(trailing.reverse, Nil) | else | this | def head = mirror.leading.head | def tail = { | val q = mirror | new Queue(q.leading.tail, q.trailing) | } | def append(x: T) = | new Queue(leading, x :: trailing) | } defined class Queue
二、信息隐藏
私有构造器及工厂方法
Java中,可以把构造器声明为私有的使其不可见。scala中,主构造器无须明确定义;不过虽然它的定义隐含于类参数及类方法体中,还是可以通过把private修饰符添加在类参数列表的前边把主构造器隐藏起来,如下所示:
scala> class Queue[T] private ( | private val leading: List[T], | private val trailing: List[T] | ) defined class Queue
夹在类名与参数之间的private修饰符表明Queue的构造器是私有的:它只能被类本身及伴生对象访问。类名Queue仍然是公开的,因此你可以继续使用这个类,但不能调用它的构造器:
scala> new Queue(List(1,2), List(3)) <console>:9: error: constructor Queue in class Queue cannot be accessed in object $iw new Queue(List(1,2), List(3)) ^
现在客户代码不能再使用Queue类的主构造器,需要有创建新队列的其他方法。一种可能方案是添加辅助构造器,如下:
def this() = this(Nil, Nil)
上面的辅助构造器可以构建空队列。通过改良,它可以带上初始队列元素列表:
def this(elems: T*) = this(elems.toList, Nil)
其中T*是重复参数的标记。
另一种可能性是添加可以初始元素序列创建队列的工厂方法。比较简洁的做法是定义与类同名的Queue对象及apply方法,如下所示:
object Queue { //用初始元素’xs‘构造队列 def apply[T](xs: T*) = new Queue[T](xs.toList, Nil) }
注意,工厂方法名为apply,因此客户可以用类似于Queue(1, 2, 3)这样的表达式创建队列。由于Queue是对象而不是函数,这个表达式会被扩展为Queue.apply(1,2,3)。
可选方案:私有类
私有构造器和私有成员是隐藏类的初始化代码和表达代码的一种方式。另一种更为彻底的方式是直接把类本身隐藏掉,仅提供能够暴露类公共接口的特质。
trait Queue[T] { def head: T def tail: Queue[T] def append(x: T): Queue[T] } object Queue { def apply[T](xs: T*):Queue[T] = new QueueImpl[T](xs.toList, Nil) private class QueueImpl[T]( private val leading: List[T], private val trailing: List[T] )extends Queue[T] { def mirror = if (leading.isEmpty) new QueueImpl(trailing.reverse, Nil) else this def head: T = mirror.leading.head def tail: QueueImpl[T] = { val q = mirror new QueueImpl(q.leading.tail, q.trailing) } def append(x: T) = new QueueImpl(leading, x :: trailing) } }
三、变化型注解
上面定义的Queue是特质,不是类型。因为它带有类型参数。结果,将不能创建类型为Queue的变量:
scala> def doesNotCompile(q: Queue) {} <console>:8: error: trait Queue takes type parameters def doesNotCompile(q: Queue) {} ^
取而代之,特质Queue能够指定参数化的类型,如Queue[String], Queue[Int],或Queue[AnyRef]:
scala> def doesCompile(q: Queue[AnyRef]) {} doesCompile: (q: Queue[AnyRef])Unit
注:根据上面定义的Queue,不同元素类型的队列之间没有子类型关系。Queue[String]对象不能被用作Queue[AnyRef]。
然而,可以用如下的方式改变Queue类定义的第一行,以要求队列协变(弹性)的子类型化:
trait Queue[+T] { def head: T def tail: Queue[T] def append(x: T): Queue[T] }
在正常的类型参数前面加上+号表明这个参数的子类型化是协变(弹性)的。
除了+号之外,还可以前缀加上-号,这表明是需要逆变的(contravariant)子类型化。如果Queue定义如下:
trait Queue[-T] { ... }
那么如果T是类型S的子类型,这将隐含Queue[S]是Queue[T]的子类型。无论类型参数是协变的,逆变的,还是非协变的,都被称为参数的变化型。可以放在类型参数前的+号和-号被称为变化型注解。
相关文章推荐
- 【Scala学习笔记】类型参数化数组
- Scala学习笔记7 - 类型参数化
- 结构大小不等于各数据类型之和--编程之道学习笔记1
- scala编程第16章学习笔记(4)——List对象的方法
- 第43讲:Scala中类型变量Bounds代码实战及其在Spark中的应用源码解析学习笔记
- Scala中链式调用风格的实现代码实战及其在Spark编程中的广泛运用之Scala学习笔记-41
- 第51讲:Scala中链式调用风格的实现代码实战及其在Spark编程中的广泛运用学习笔记
- UNIX环境编程学习笔记(6)——文件I/O之判断文件类型
- Scala学习笔记--函数式编程
- Scala学习笔记3--类型推演
- 第81讲:Scala中List的构造时的类型约束逆变、协变、下界详解学习笔记
- Scala学习笔记(三) - 基础类型
- Scala学习笔记--集合类型Queue,Set
- scala编程第17章学习笔记(3)
- 第73讲:Scala界面和事件处理编程进阶实战学习笔记
- Scala学习笔记(3):纯函数式编程的一些思想和技巧
- scala编程第16章学习笔记(3)——List类的高阶方法
- scala学习笔记-类型参数中协变(+)、逆变(-)、类型上界(<:)和类型下界(>:)的使用
- scala编程第18章学习笔记——有状态的对象
- Scala类型约束代码实战及其在Spark中的应用源码解析之Scala学习笔记-39