快学Scala学习笔记及习题解答(17-18类型参数与高级类型)

本文Scala使用的版本是2.11.8

第17章 类型参数

17.1 泛型

泛型类

class Pair[T, S](val first: T, val second: S)

// 实例化
val p = new Pair(42, "String")
val p2 = new Pair[Any, Any](42, "String")

泛型函数

def getMiddle[T](a: Array[T]) = a(a.length / 2)

// 调用
getMiddle(Array("Mary", "had", "a", "little", "lamb"))

// 下面具体函数保存到f
val f = getMiddle[String] _

17.2 界定

类型变量界定

  1. 上界(<:

    class Pair[T <: Comparable[T]](val first: T, val second: T) {
      def smaller = if (first.compareTo(second) < 0) first else second
    }
  2. 下界(>:

    def replaceFirst[R >: T](newFirst: R) = new Pair(newFirst, second)

视图界定(<%

// <%关系意味着T可以被隐式转换成Comparable[T]
class Pair[T <% Comparable[T]]

上下文界定

上下文界定的形式为T:M,其中M是另一个泛型类。它要求必须存在一个类型为M[T]的“隐式值”。

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
}

Manifest上下文界定

使用基本类型实例化一个泛型Array[T],需要一个Manifest[T]对象。

def makePair[T: Manifest](first: T, second: T) {
    val r = new Array[T](2)
    r(0) = first
    r(1) = second
    r
}

调用makePair(4, 9),编译器将定位到隐式的Manifest[Int]并实际上调用makePair(4, 9)(intManifest),这样返回基本类型的数组int[2]。

多重界定

// 可以同时有上界和下界,但不能同时又多个上界或多个下界
T >: Lower <: Upper

// 可以实现多个特质
T <: Comparable[T] with Serializable with Cloneable

// 多个视图界定
T <% Comparable[T] <% String

// 多个上下文界定
T : Ordering : Manifest

17.3 类型约束

总共有三种关系:

  • T =:= U
  • T <:< U
  • T <%< U

这些约束将会测试T是否等于U,是否为U的子类型,或能否被视图(隐式)转换为U。使用时需要添加“隐式类型证明参数”:

class Pair[T](val first: T, val second: T)(implicit ev: T <:< Comparable[T])

在泛型类中定义只能在特定条件下使用的方法

示例一:

class Pair[T](val first: T, val second: T) {
    def smaller(implicit ev: T <:< Ordered[T]) =
        if (first < second) first else second
}

// 可以构造出Pair[File],但只有当调用smaller方法时,才会报错

示例二:Option类的orNull方法

val friends = Map("Fred" -> "Barney", ...)
val friendOpt = friends.get("Wilma") // Option[String]
val friendOrNull = friendOpt.orNull // 要么是String,要么是null

// 对于值类型,仍然可以实例化Option[Int],只要别使用orNull就好

改进类型推断

def firstLast[A, C <: Iterable[A]](it: C) = (it.head, it.last)

// 当执行如下代码时:firstLast(List(1, 2, 3)),将推断出非期望的类型[Nothing, List[Int]]
// 因为是同一个步骤中匹配的A和C,要解决这个问题,必须先匹配C,然后再匹配A:
def firstLast[A, C](it: C)(implicit ev: C <:< Iterable[A]) = (it.head, it.last)

17.4 型变

协变:C[+T]

如果A是B的子类,那么C[A]是C[B]的子类。

class Pair[+T](val first: T, val second: T)

+号意味着该类型是与T协变的,也就是说,它与T按同样的方向型变。由于Student是Person的子类型,Pair[Student]也是Pair[Person]的子类型。

逆变:C[-T]

如果A是B的子类,那么C[A]是C[B]的父类。

trait Friend[-T] {
    def befriend(someone: T)
}

// 函数
def makeFriendWith(s: Student, f: Friend[Student]) { f.befriend(s) }

// 下面关系
class Person extends Friend[Person]
class Student extends Person
val susan = new Student
val fred = new Person

// 可以使用以下调用,产出Student结果,可以被放入Array[Person]
makeFriendWith(susan, fred)

17.5 协变和逆变点

class Pair[+T](var first: T, var second: T)

会报错,说在如下的setter方法中,协变的类型T出现在了逆变点:first_=(value: T)

参数位置是逆变点,而返回类型的位置是协变点。

在函数参数中,型变是反转过来的,参数是协变的。例如(Iterable[+A]的foldLeft方法):

foldLeft[B](z: B)(op: (B, A) => B): B
               -       +  +     -   +

A现在位于协变点。

考虑以下不可变对偶的replaceFirst方法:

class Pair[+T](val first: T, val second: T) {
    def replaceFirst(newFirst: T) = new Pair[T](newFirst, second)
}

编译器会报错,因为类型T出现在了逆变点,解决方法是给方法加上另一个类型参数:

class Pair[+T](val first: T, val second: T) {
    def replaceFirst[R >: T](newFirst: R) = new Pair[R](newFirst, second)
}

17.6 对象不能泛型

不能给对象添加类型参数。例如:

abstract class List[+T] {
    def isEmpty: Boolean
    def head: T
    def tail: List[T]
}

class Node[T](val head: T, val tail: List[T]) extends List[T] {
    def isEmpty = false
}

object Empty[T] extends List[T] // 错误

// 解决方法一:
class Empty[T] extends List[T] {
    def isEmpty = true
    def head = throw new UnsupportedOperationException
    def tail = throw new UnsupportedOperationException
}

// 解决方法二:
object Empty extends List[Nothing]
// 根据协变规则,List[Nothing]可以被转换成List[Int]

17.7 类型通配符

_为Scala中使用的通配符。

// 协变
def process(people: java.util.List[_ <: Person]

// 逆变
import java.util.Comparator
def min[T](p: Pair[T])(comp: Comparator[_ >: T])

17.8 习题解答


1. 定义一个不可变类Pair[T,S], 带一个swap方法,返回组件交换过位置的新对偶。


2. 定义一个可变类Pair[T],带一个swap方法,交换对偶中组件的位置。


3. 给定类Pair[T, S] ,编写一个泛型方法swap,接受对偶作为参数并返回组件交换过位置的新对偶。


4. 在17.3节中,如果我们想把Pair[Person]的第一个组件替换成Student,为什么不需要给replaceFirst方法定一个下界?


5. 为什么RichInt实现的是Comparable[Int]而不是Comparable[RichInt]?


6. 编写一个泛型方法middle,返回任何Iterable[T]的中间元素。举例来说,middle(“World”)应得到’r’。


7. 查看Iterable[+A]特质。哪些方法使用了类型参数A?为什么在这些方法中类型参数位于协变点?


8. 在17.10节中,replaceFirst方法带有一个类型界定。为什么你不能对可变的Pair[T]定义一个等效的方法?

def replaceFirst[R >: T](newFirst: R) { first = newFirst } // 错误


9. 在一个不可变类Pair[+T]中限制方法参数看上去可能有些奇怪。不过,先假定你可以在Pair[+T]定义

def replaceFirst(newFirst: T)

问题在于,该方法可能会被重写(以某种不可靠的方式)。构造出这样的一个示例。定义一个Pair[Double]的类型NastyDoublePair,重写replaceFirst方法,用newFirst的平方根来做新对偶。然后对实际类型为NastyDoublePair的Pair[Any]调用replaceFirst(“Hello”)。


10. 给定可变类Pair[S,T],使用类型约束定义一个swap方法,当类型参数相同时可以被调用。

第18章 高级类型

18.1 单例类型

子类示例


class Document {
    def setTitle(title: String): this.type = { ..., this }
    def setAuthor(author: String) = { ..., this }
    ...
}

class Book extends Document {
    def addChapter(chapter: String) = { ...; this }
    ...
}

注意上面红字,这样就可以进行下面这样的调用:


val book = new Book()
book.setTitle("Scala for the Impatient").addChapter(chapter1)

对象示例


object Title

class Document {
    private var useNextArgAs: Any = null
    def set(obj: Title.type): this.type = { useNextArgAs = obj; this }
    ...
}

注意上面红字,如果使用obj: title则指代的是单例对象,而不是类型。

18.2 类型投影

嵌套类从属于它的外部对象,如下:

import scala.collection.mutable.ArrayBuffer

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

    private val members = new ArrayBuffer[Member]

    def join(name: String) = {
        val m = new Member(name)
        members += m
        m
    }
}

这样每个网络实例都有它自己的Member类。

如果不想所有地方使用 “每个对象自己的内部类” 的话,可以用类型投影Network#Member意思是任何Network的Member。

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

18.3 路径

com.demo.chatterobj.Member这样的表达式被称之为路径。而且在最后的类型之前,路径的所有组成部分都必须是 “稳定的” ,即它必须指定到单个、有穷的范围,必须是以下当中的一种:

  • 对象
  • val
  • this、super、super[S]、C.this、C.super或C.super[S]

18.4 类型别名

可以用type关键字创建一个简单的别名:

class Book {
    import scala.collection.mutable._
    type Index = HashMap[String, (Int, Int)]
    ...
}

然后就可以用Book.Index代替scala.collection.mutable.HashMap[String, (Int, Int)]。

另外,类型别名必须被嵌套在类或对象中,它不能出现在Scala文件的顶层。

18.5 结构类型

指的是一组关于抽象方法、字段和类型的规格说明。例如:


def appendLines(target: { def append(str: String): Any }, lines: Iterable[String]) {
    for (l 

18.6 复合类型

定义形式如下:

T1 with T2 with T3 ...

其中T1、T2、T3等是类型。要想成为该复合类型的实例,某个值必须满足每一个类型的要求才行。即这样的类型也被称做交集类型。

例如:

val image = new ArrayBuffer[java.awt.Shape with java.io.Serializable]
// 只能添加那些即时形状(Shape)也是可被序列化的对象。

val rect = new Rectangle(5, 10, 20, 30)
image += rect // ok,Rectangle是Serializable的
image += new Area(rect) // 错误,Area是Shape但不是Serializable的

18.7 中置类型

一个带有两个类型参数的类型,以中置语法表示,类型名称写在两个类型参数之间,例如

String Map Int

// 而不是
Map[String, Int]

在Scala中,可以这样定义:

type x[A, B] = (A, B)

// 此后就可以写
String x Int
// 而不是
(String, Int)

另外,中置类型操作符都拥有相同的优先级,而且是左结合的。即:

String x Int x Int
// 意思是:
((String, Int), Int)

18.8 存在类型

定义方式是在类型表达式之后跟上forSome { ... },花括号中包含了type和val的声明。

Array[T] forSome { type T <: jComponent }

Scala的类型通配符只不过是存在类型的“语法糖”,例如:

Map[_, _]
// 等同于
Map[T, U] forSome { type T; type U }

只不过forSome可以使用更复杂的关系:

def process[M <: n.Member forSome { val n: Network }](m1: M, m2: M) = (m1, m2)

// 该方法将会接受相同网络的成员,但拒绝那些来自不同网络的成员

val chatter = new Network
val myFace = new Network
val fred = chatter.join("Fred")
val wilma = chatter.join("Wilma")
val barney = myFace.join("Barney")
process(fred, wilma) // ok
process(fred, barney) // 错误

18.9 Scala类型系统

类型语法说明
类或特质class C …, trait C …第5、10章
元组(T1, …, Tn)第4.7节
函数类型(T1, …, Tn) => T
带注解的类型T@A第15章
参数化类型A[T1, …, Tn]第17章
单例类型值.type第18章
类型投影O#I第18章
复合类型T1 with T2 with … with Tn { 声明 }第18章
中置类型T1 A T2第18章
存在类型T forSome { type和val声明 }第18章

18.10 自身类型

自身类型(self type)的声明来定义特质:this: 类型 =>,这样的特质只能被混入给定类型的子类当中。

trait Logged {
    def log(msg: String)
}

trait LoggedException extends Logged {
    this: Exception =>
      def log() { log(getMessage()) }
      // 可以调用getMessage,因为this是一个Exception
}
val f = new JFrame with LoggedException
// 错误:JFrame不是LoggedException的自身类型Exception的子类型

多个类型要求时,可以用复合类型:

this: T with U with ... =>

18.11 依赖注入

在Scala中,可以通过特质和自身类型达到一个简单的依赖注入的效果。例如日志功能:

trait Logger { def log(msg: String) }

// 有该特质的两个实现,ConsoleLogger和FileLogger。

// 用户认证特质有一个对日志功能的依赖,用于记录认证失败:
trait Auth {
    this: Logger =>
        def login(id: String, password: String): Boolean
}

// 应用逻辑有赖于上述两个特质:
trait App {
    this: Logger with Auth =>
        ...
}

// 然后,可以像这样来组装我们的应用:
object MyApp extends App with FileLogger("test.log") with MockAuth("users.txt")

蛋糕模式给出了更好的设计,在这个模式当中,对每个服务都提供一个组件特质,该特质包含:

  • 任何所依赖的组件,以自身类型表述
  • 描述服务接口的特质
  • 一个抽象的val,该val将被初始化成服务的一个实例
  • 可以有选择性地包含服务接口的实现
trait LoggerComponent {
    trait Logger { ... }
    val logger: Logger
    class FileLogger(file: String) extends Logger { ... }
    ...
}

trait AuthComponent {
    this: LoggerComponent => // 让我们可以访问日志器

    trait Auth { ... }
    val auth: Auth
    class MockAuth(file: String) extends Auth { ... }
    ...
}

// 组件配置
object AppComponents extends LoggerComponent with AuthComponent {
    val logger = new FileLogger("test.log")
    val auth = new MockAuth("users.txt")
}

18.12 抽象类型

类或特质可以定义一个在子类中被具体化的抽象类型(abstract type)。例如:

trait Reader {
  type Contents
  def read(fileName: String): Contents
}

// 在这里,类型Contents是抽象的,具体的子类需要指定这个类型:
class StringReader extends Reader {
  type Contents = String
  def read(fileName: String) = Source.fromFile(fileName, "UTF-8").mkString
}

class ImageReader extends Reader {
  type Contents = BufferedImage
  def read(fileName: String) = ImageIO.read(new File(fileName))
}

同样的效果也可以通过类型参数来实现:

trait Reader[C] {
  def read(fileName: String): C
}

class StringReader extends Reader[String] {
  def read(fileName: String) = Source.fromFile(fileName, "UTF-8").mkString
}

class ImageReader extends Reader[BufferedImage] {
  def read(fileName: String) = ImageIO.read(new File(fileName))
}

Scala的经验法则是:

  • 如果类型是在类被实例化时给出,则使用类型参数
  • 如果类型是在子类中给出的,则使用抽象类型

当有多个类型依赖时,抽象类型用起来更方便——可以避免使用一长串类型参数:

trait Reader {
  type In
  type Contents
  def read(in: In): Contents
}

class ImageReader extends Reader {
  type In = File
  type Contents = BufferedImage
  def read(file: In) = ImageIO.read(file)
}

抽象类型可以有类型界定

trait Listener {
  type Event <: java.util.EventObject
  ...
}

// 子类必须提供一个兼容的类型
trait ActionListener extends Listener {
  type Event = java.awt.event.ActionEvent  // OK,这是一个子类型
}

18.13 家族多态

以客户端Java的事件处理为例:

泛型类型示例

trait Listener[E] {
    def occurred(e: E): Unit
}

// 事件源需要一个监听器的集合,和一个触发这些监听器的方法
trait Source[E, L <: Listener[E]] {
    private val listeners = new ArrayBuffer[L]
    def add(l: L) { listeners += l }
    def remove(l: L) { listeners -= l }
    def fire(e: E) {
        for (l <- listeners) l.occurred(e)
    }
}

// 按钮触发动作事件,监听器类型
trait ActionListener extends Listener[ActionEvent]

// Button类混入Source特质
class Button extends Source[ActionEvent, ActionListener] {
    def click() {
        fire(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "click"))
    }
}

// ActionEvent类将事件源设置为this,但是事件源的类型为Object。可以用自身类型来让它也是类型安全的:
trait Event[S] {
    var source: S = _
}

trait Listener[S, E <: Event[S]] {
    def occurred(e: E): Unit
}

trait Source[S, E <: Event[S], L <: Listener[S, E]] {
    this: S =>
       private val listeners = new ArrayBuffer[L]
       def add(l: L) { listeners += l }
       def remove(l: L) { listeners -= l }
       def fire(e: E) {
           e.source = this // 这里需要自身类型
           for (l <- listeners) l.occurred(e)
       }
}

// 定义按钮
class ButtonEvent extends Event[Button]

trait ButtonListener extends Listener[Button, ButtonEvent]

class Button extends Source[Button, ButtonEvent, ButtonListener] {
    def click() { fire(new ButtonEvent) }
}

抽象类型示例

trait ListenerSupport {
    type S <: Source
    type E <: Event
    type L <: Listener

    trait Event {
        var Source: S = _
    }

    trait Listener {
        def occurred(e: E): Unit
    }

    trait Source {
        this: S =>
            private val listeners = new ArrayBuffer[L]
            def add(l: L) { listeners += l }
            def remove(l: L) { listeners -= l }
            def fire(e: E) {
                e.source = this
                for (l <- listeners) l.occurred(e)
            }
    }
}

// 
object ButtonModule extends ListenerSupport {
    type S = Button
    type E = ButtonEvent
    type L = ButtonListener

    class ButtonEvent extends Event
    trait ButtonListener extends Listener
    class Button extends Source {
        def click() { fire(new ButtonEvent) }
    }
}

// 使用按钮
object Main {
    import ButtonModule._

    def main(args: Array[String]) {
        val b = new Button
        b.add(new ButtonListener {
            override def occurred(e: ButtonEvent) { println(e) }
        })
        b.click()
    }
}

18.14 高级类型

trait Container[E] {
    def += (e: E): Unit
}

// Iterable依赖一个类型构造器来生成结果,以C[X]表示。这使得Iterable成为一个高等类型
trait Iterable[E, C[X] <: Container[X]] {
    def iterator(): Iterator[E]
    def build[F](): C[F]
    def map[F](f: (E) => F): C[F] = {
        val res = build[F]()
        val iter = iterator()
        while (iter.hasNext) res += f(iter.next())
        res
    }
}

class Range(val low: Int, val high: Int) extends Iterable[Int, Buffer] {
    def iterator() = new Iterator[Int] {
        private var i = low
        def hasNext = i <= high
        def next() = { i += 1; i - 1 }
    }

    def build[F]() = new Buffer[F]
}

class Buffer[E: Manifest] extends Iterable[E, Buffer] with Container[E] {
    private var capacity = 10
    private var length = 0
    private var elems = new Array[E](capacity)

    def iterator() = new Iterator[E] {
        private var i = 0
        def hasNext = i < length
        def next() = { i += 1; elems(i - 1) }
    }

    def build[F]() = new Buffer[F]

    def +=(e: E) {
        if (length == capacity) {
            capacity = 2 * capacity
            val nelems = new Array[E](capacity)
            for (i <- 0 until length) nelems(i) = elems(i)
            elems = nelems
        }
        elems(length) = e
        length += 1
    }
}

18.15 习题解答


1. 实现一个Bug类,对沿着水平线爬行的虫子建模。move方法向当前方向移动,turn方法让虫子转身,show方法打印出当前的位置。让这些方法可以被串接调用。例如:

bugsy.move(4).show().move(6).show().turn().move(5).show()

上述代码应显示4 10 5。


2. 为前一个练习中的Bug类提供一个流利接口,达到能编写如下代码的效果:

bugsy move 4 and show and then move 6 and show turn around move 5 and show


3. 完成18.1节中的流利接口,以便我们可以做出如下调用:

book set Title to “Scala for the Impatient” set Author to “Cay Horstmann”


4. 实现18.2节中被嵌套在Network类中的Member类的equals方法。两个成员要想相等,必须属于同一个网络。


5. 考虑如下类型别名

type NetworkMember = n.Member forSome { val n : Network }

和函数

def process(m1: NetworkMember, m2: NetworkMember) = (m1, m2)

这与18.8节中的process函数有什么不同?

与18.8不同,允许不同网络作为参数


6. Scala类库中的Either类型可以被用于要么返回结果,要么返回某种失败信息的算法。编写一个带有两个参数的函数:一个已排序整数数组和一个整数值。要么返回该整数值在数组中的下标,要么返回最接近该值的元素的下标。使用一个中置类型作为返回类型。


7. 实现一个方法,接受任何具备如下方法的类的对象和一个处理该对象的函数。
调用该函数,并在完成或有任何异常发生时调用close方法。

def close(): Unit


8. 编写一个函数printValues,带有三个参数f、from和to,打印出所有给定区间范围内的输入值经过f计算后的结果。这里的f应该是任何带有接受Int产出Int的apply方法的对象。例如:

printValues((x: Int) => x*x, 3, 6) //将打印 9 16 25 36
printValues(Array(1, 1, 2, 3, 5, 8, 13, 21, 34, 55), 3, 6) //将打印 3 5 8 13


9. 考虑如下对物理度量建模的类:

abstract class DimT {
    protected def create(v: Double): T
    def +(other: Dim[T]) = create(value + other.value)
    override def toString() = value + “ “ + name
}

以下是具体子类:

class Seconds(v: Double) extends DimSeconds {
    override def create(v: Double) = new Seconds(v)
}

但现在不清楚状况的人可能会定义

class Meters(v: Double) extends DimSeconds {
    override def create(v: Double) = new Seconds(v)
}

允许米(Meters)和秒(Seconds)相加。使用自身类型来防止发生这样的情况。


10. 自身类型通常可以被扩展自身的特质替代,但某些情况下使用自身类型会改变初始化和重写的顺序。构造出这样的一个示例。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值