协变 Covariance
在期望接收一个基类(父类) 实例的集合的地方,能够使用一个子类实例的集合的能力叫做协变。
//基类
var objects: List[Any] = null;
//子类
val ints = List(1,2,3,4)
objects = ints //scala 会编译错误
如上面的代码,将 int 数据类型的集合赋值给 Any 类型的集合,就是协变。
Scala 的 List[+T] 支持协变
逆变 Contravariance
在期望接收一个子类实例的集合的地方,能够使用一个基类(父类) 实例的集合的能力叫做逆变。
//子类
var ints: List[Int] = null;
//基类
val objects = List("3","4")
ints = objects //scala 会编译错误
如上面的代码,将 objects 父类数据类型的集合赋值给 int 类型的集合,就是逆变。
Scala 的 List[+T] 不支持逆变
如何支持协变 -- 协变定义上界
定义 Pet 和 Dog
case class Pet(name: String)
case class Dog(name:String) extends Pet(name)
有一个接收 List[Pet] 的方法
def doPets(pets: List[Pet]) = {
//xxxx
}
如果使用 List[Dog] 的集合传入该方法会报编译错误,如果想让该方法支持协变,则可以像如下方法定义:
def doPets[T <: Pet](pets: List[T]) {
// xxxx
}
T <: Pet
表明由 T 表示的类派生自 Pet 类,也即 T 为 Pet 的子类。这个语法定义了一个上界。Pet 为 T 的上界,即往父类方向坐了限制。
如何支持逆变 -- 逆变定义下界
//逆变,限制了下界, T 必须为 Apple 或其超类
def writeTo[T >: Apple](apples: List[T]): Unit = {
}
逆变会决定下界,然后你可以在上述的 apples 中添加 Apple 或其父类。
PECS 原则
《Effective Java》给出精炼的描述:producer-extends, consumer-super(PECS)。
- 协变限制数据来源,生产者,保证生产的产品为 T 或其子类。
- 逆变限制数据消费,保证用来消费的数据必须时 T 或其父类。
//copy方法限制了拷贝源src必须是T或者是它的子类,
// 而拷贝目的地dest必须是T或者是它的父类,这样就保证了类型的合法性。
def copy[S, D >: S](src: List[S], dest: List[D]): Unit = {
}
- copy 方法,限制 src 来源必须是 S 及其子类,限制 dest 是 S及其父类,这样才能去接收。
在scala泛型中获取其 Class[T]
需求:获取一个泛型 T 的 class 类型的 Class[T],有两种方法。
获取方式1
def getClassT[T](obj: T): Class[T] = {
val res = obj.getClass.asInstanceOf[Class[T]]
res
}
更优雅的获取方式
def getClassT[T](obj: T)(implicit m: Manifest[T]): Class[T] = {
val res: Class[T] = m.runtimeClass.asInstanceOf[Class[T]]
res
}