Scala学习笔记 A3/L2篇 - 类型参数 Type Parameters

教材:快学Scala

chapter 17. 类型参数 Type Parameters

类、特质、方法、函数都可以有类型参数
类型界定 T <: UpperBound T >: LowerBound T <% ViewBound T : ContextBound
类型约束 implicit ev: T <:< UpperBound
协变+T适用于表示输出的类型参数,如不可变集合中的元素
逆变-T适用于表示输入的类型参数,如函数参数

17.1 泛型类 Generic Classes

带有一个或多个类型参数的类是泛型的
class Pair[T, S](val first: T, val second: S)

17.2 泛型函数 Generic Functions

def getMiddle[T](a: Array[T]) = a(a.length / 2)
val f = getMiddle[String] _ // 具体的函数,保存到f

17.3 类型变量界定 Bounds for Type Variables

  • 上界 T <: Comparable[T] (T是Comparable[T]的子类型)
    为保证firstcompareTo方法,即保证T是Comparable[T]的子类型
class Pair[T <: Comparable[T]](val first: T, val second: T) {
    def smaller = if (first.compareTo(second) < 0) first else second
}

val p = new Pair("Fred", "Brook")
println(p.smaller) // Brook

val p2 = new Pair(4, 2) // 出错 inferred type arguments [Int] do not conform to class Pair's type parameter bounds [T <: Comparable[T]]
  • 下界 R >: T (T是R的子类型)
    替换Pair的first的方法,对于Pair[Student]希望能用Person实例替换第一个元素,生成新的Pair[Person]
class Pair[T <: Comparable[T]](val first: T, val second: T) {
    def replaceFirst[R >: T](newFirst: R) = new Pair[R](newFirst, second)
}

17.4 视图界定 View Bounds

  • 视图界定 T <% Comparable[T] (T可以被隐式转换成Comparable[T])
    Int可以隐式转换为RichIntRichInt实现了Comparable[Int],因此Int可以隐式转换为Comparable[Int]
class Pair[T <% Comparable[T]](val first: T, val second: T) {
    def smaller = if (first.compareTo(second) < 0) first else second
}

val p = new Pair("Fred", "Brook") // 说明子类可以隐式转换为父类
println(p.smaller) // Brook

val p2 = new Pair(4, 2)
println(p2.smaller) // 2

使用Ordered特质更好,可以直接用<操作符比较而不是compareTo
java.lang.String 实现了Comparable[String]但没有实现Ordered[String]故不能用<:
但是java.lang.String 可以隐式转换为RichStringRichStringOrdered[String]的子类型

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

17.5 上下文界定 Context Bounds

  • 上下文界定 T : Ordering 必须存在一个类型为Ordering[T] 的隐式值
    声明一个使用隐式值的方法时,需要添加一个隐式参数(implicit parameter)
    隐式值比隐式转换更加灵活
class Pari[T : Ordering](val first: T, val second: T) {
    def smaller(implicit ord: Ordering[T]) = // 隐式值 ord
        if (ord.compare(first, second) < 0) first else second
}

17.6 Manifest Context Bound

*what the fuck*
在JVM中,泛型相关的类型信息在编译期和运行期是被抹掉的。
Manifest在官方文档中的说明:

Like scala.reflect.Manifest, TypeTags can be thought of as objects which carry along all type information available at compile time, to runtime. For example, TypeTag[T] encapsulates the runtime type representation of some compile-time type T. Note however, that TypeTags should be considered to be a richer replacement of the pre-2.10 notion of a Manifest, that are additionally fully integrated with Scala reflection.

17.7 多重界定 Multiple Bounds

T >: Lower <: Upper // 同时有上界和下界,但只能各有一个
T <: Comparable[T] with Serializable with Clonable // 可以要求T同时实现多个trait
T <% Comparable[T] <% String // 可以有多个视图界定
T : Ordering : Manifest // 可以有多个上下文界定

17.8 类型约束 Type Constraints

  • 类型约束 另一种限定类型的方式
    T =:= U // 测试T是否等于U
    T <:< U // 测试T是否为U的子类型
    T <%< U // 测试T能否被视图(隐式)转换为U
    使用类型约束时需要添加隐式类型证明参数(implicit evidence parameter)
    class Pair[T](val first: T, val second: T)(implicit ev: T<:< Comparable[T])
    类型约束之于类型界定的优势:
  1. 类型约束可以在具体的方法约束T,而不是对整个类
    例1.1
class Pair[T](val first: T, val second: T) {
    def smaller(implicit ev: T <:< Ordered[T]) = 
        if (first < second) first else second
}

val p = Pair[File](...) // 只要不调用p.smaller就不会报错!

例1.2 Option类的orNull方法
orNull用于将Option值转换为Java可用的null值,但是对值类型(如Int)是没有null值的
orNull实现带有约束Null <:< A,仍然可以实例化Option[Int]只要别使用noNull就行

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

val friendStringOpt = friends.get("Fred") // Option[String] = Some(Barney)
val friendStringOrNull = friends.get("Fred").orNull // String = Barney
  1. 类型约束可用于改进类型推断
def firstLast[A, C <: Iterable[A]](it: C) = (it.head, it.last)
firstLast(List(1, 2, 3))
// error: inferred type arguments [Nothing,List[Int]] 
// do not conform to method firstLast's type parameter bounds [A,C <: Iterable[A]]

A是Nothing的原因:单凭List(1,2,3)无法推断出A是什么,因为它是在同一个步骤中匹配A和C的
解决方法:先匹配C,再匹配A

def firstLast[A, C](it: C)(implicit ev: C <:< Iterable[A]) = (it.head, it.last)
firstLast(List(1, 2, 3)) // (Int, Int) = (1,3)

17.9 型变 Variance

Student是Person的子类,那么Pair[Student]和Pair[Person]的关系是什么?
正常来说是没任何关系的,要添加关系需要用到型变描述

  • 协变(covariant) Pair[+T] Pair[T]与T按同样方向型变,Pair[Student]是Pair[Person]的子类
    class Pair[+T](val first: T, val second: T)
  • 逆变(contravariant) Friend[-T] Friend[Person]是Friend[Student]的子类
    trait Friend[-T] { def befriend(someone: T) }
  • 不变(invariant) Pair[T] 没有任何关系
  • 可以同时使用协变和逆变,如Function1[-A, +R]
    Function[A, R] 等价于 A => R 即输入参数为逆变,返回结果为协变
trait Friend[-T] {
    def befriend(someone: T)
}
def friends(students: Array[Student], find: Function1[Student, Person]) = 
    for (s <- students) yield find(s)

def findStudent(p: Person) : Student = {...}
// findStudent可以作为find的实参
// 输入:A = Student, A- = Person
// 输出:R = Person, R+ = Student
...
val arrStuFriends = friends(arrStudents, findStudent) 

17.10 协变点和逆变点 Co- and Contravariant Positions

对于某个对象消费的值(the values an object consumes)适用逆变(-T),对于它产出的值(the values it produces)则适用协变(+T)。
如果一个对象同时消费和产出某值,则类型应该是不变(invariant)

  • 逆变点(contravariant position):函数的参数位置,或函数参数(function parameter)的返回类型位置
  • 协变点(covariant position):函数的返回类型位置,或函数参数(function parameter)的参数位置
  • 不变点(invariant posision):参数位置和返回类型位置都同时出现T的方法
    例如Scala中的数组是invariant,即Array[Student]不能转换为Array[Person],反过来也不行
    在Java可以将Student[]数组转换为Person[]数组,但如果试图将非Student对象放入数组时将跑出ArrayStoreException,在Scala中编译器会拒绝可能引发类型错误的程序通过编译。
    参数位置是逆变点,返回类型的位置是协变点;但是函数参数则反过来,函数参数的参数是协变点,函数参数的返回类型的位置是逆变点
// 错误:协变T出现在【first_=所属的类型T】逆变点
class Pair_t1[+T](var first: T, var second: T)

// 错误1:协变T出现在【replaceFirst所属的类型(newFirst: T)Pair[T]】不变点
// 错误2:协变T出现在【newFirst所属的类型T】逆变点
class Pair_t2[+T](val first: T, val second: T) {
    def replaceFirst(newFirst: T) = new Pair[T](newFirst, second)
}

class Pair_t3[+T](val first: T, val second: T) {
    // 正确:引入一个新的类型R并且做类型界定,此时R是invariant,invariant出现在逆变点没有问题
    def replaceFirst_t1[R >: T](newFirst: R) = new Pair[R](newFirst, second)
    // 正确:用类型约束也可以
    def replaceFirst_t2[R](newFirst: R)(implicit ev: T <:< R) = new Pair[R](newFirst, second)
}

17.11 Objects Can’t Be Generic

object Empty[T] extends List[T] // 错误
解决方法:继承List[Nothing],因为Nothin是所有类型的子类型

abstract class List[+T] {...}
class Node[T](val head: T, val tail: List[T]) extends List[T] {...}
object Empty extends List[Nothing]
//Empty -> List[Nothing] -> List[T]

val lst = new Node(42, Empty) // 正确

17.12 类型通配符 Wildcards

在Java中,所有泛型类型都是invariant,但可以使用通配符改变类型
Scala也有对应的通配符表示法
Java: void makeFriends(Pair people) // 可以用List作为参数调用
Scala: def makeFriends(people: Pair[_ <: Person])
// 如果Pair是协变的,不需要用通配符表示;如果Pair是不变的,可以用通配符。

练习答案

class Pair[T, S](val first: T, val second: S) { 
    def swap = new Pair(second, first) 
    def p = println(first, second) 
}
val p = new Pair(1, "hello")
p.swap.p
class Pair[T](var first: T, var second: T) {
    def swap = {
        val tmp = first
        first = second
        second = tmp
        this
    }
    def p = println(first, second) 
}
val p = new Pair(1, "world")
p.swap.p
class Pair[T, S](val first: T, val second: S) {
    def p = println(first, second) 
}

// def swap[T, S](p: Pair[T, S]) = {
//     // 
//     def __swap[T, S](first: T)(second: S)(p: Pair[T, S]) = new Pair(p.second, p.first)
//     __swap(p.first)(p.second)(p)
// }
def swap[T, S](p: Pair[T, S]) = new Pair(p.second, p.first)
val p = new Pair(1, "world")
swap(p).p
  1. 里氏替换原则
  2. Int 隐式转换 RichInt -> Comparable[Int], 即 Int 隐式转换 Comparable[Int],
    只有实现Comparable[Int] 才方便使用视图界定 T <% Comparable[T]。
    若实现的是Comparable[RichInt],则使用视图界定时要求 T为RichInt,而整型字面量默认为Int需要先转换为RichInt不方便。
// def middle[T, I](it: I)(implicit ev: I <:< Iterable[T]): Option[T] = it.take(it.size / 2 + 1).lastOption
// test1: middle(Seq(1, 2, 3, 4, 5)) // Option[Int] = Some(3)
// test2: middle("hello") // error: Cannot prove that String <:< Iterable[T].

// github上看到的解答,暂时不懂隐式类型证明参数中的=>的含义
def middle[T, I](it: I)(implicit ev: I => Iterable[T]): Option[T] = it.take(it.size / 2 + 1).lastOption
// test1: middle(Seq(1, 2, 3, 4, 5)) // Option[Int] = Some(3)
// test2: middle("hello") // Option[Char] = Some(l)
// error: type mismatch;
// found   : newFirst.type (with underlying type R)
// required: T
class Pair[T](var first: T, var second: T) {
    def replaceFirst[R >: T](newFirst: R) {first = newFirst}
}
class Pair[S, T](var first: S, var second: T) {
    def p = println(first, second) 

    // 写成 S =:= T 就出错,不懂
    def swap(implicit ev: T =:= S) = {
        val tmp = first.asInstanceOf[T]
        first = second
        second = tmp        
        this
    }
}

// 写成外部函数就出错,不懂
// def swap[S, T](p: Pair[S, T])(implicit ev: T =:= S) = {
//     val tmp = p.first.asInstanceOf[T]
//     p.first = p.second
//     p.second = tmp
//     p
// }

val p1 = new Pair(1, 5)
p1.swap.p // (5,1)
val p2 = new Pair(1, "hello")
p2.swap // error: Cannot prove that String =:= Int.

你可能感兴趣的:(Scala学习笔记 A3/L2篇 - 类型参数 Type Parameters)