教材:快学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]的子类型)
为保证first
有compareTo
方法,即保证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
可以隐式转换为RichInt
,RichInt
实现了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
可以隐式转换为RichString
,RichString
是Ordered[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 typeT
. Note however, thatTypeTags
should be considered to be a richer replacement of the pre-2.10 notion of aManifest
, 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])
类型约束之于类型界定的优势:
- 类型约束可以在具体的方法约束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
- 类型约束可用于改进类型推断
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 extends Person> 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
- 里氏替换原则
- 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.