本文原文出处: http://blog.csdn.net/bluishglc/article/details/52584401 严禁任何形式的转载,否则将委托CSDN官方维护权益!
型变试图在规范和回答这样一个问题:如果T’是T的一个子类,那么Container[T’]应该被看做是Container[T]的子类吗?对于这个问题有三种可能的结果,它们分别如下:
种类 | 含义 | Scala标记 |
---|---|---|
协变 Covariant | C[T’] 是 C[T] 的子类 | [+T] |
逆变 Contravariant | C[T] 是 C[T’]的子类 | [-T] |
不变 Invariant | C[T] 和 C[T’]无关 | [T] |
“不变”性表述为:虽然T’是T的一个子类,但是Container[T’]和Container[T]之间无任何关联,不存在任何父子关系。
协变是比较符合人们的正常思维的,因此理解起来并不困难,简单解释就是:如果T’是T的一个子类,那么Container[T’]应该被看做是Container[T]的子类。下面的例子展示了协变[+T]的特性。
scala> class Covariant[+T]
defined class Covariant
scala> val cv: Covariant[AnyRef] = new Covariant[String]
cv: Covariant[AnyRef] = Covariant@4035acf6
scala> val cv: Covariant[String] = new Covariant[AnyRef]
<console>:6: error: type mismatch;
found : Covariant[AnyRef]
required: Covariant[String]
val cv: Covariant[String] = new Covariant[AnyRef]
^
逆变正好是协变的”反”方向:如果T’是T的一个子类,那么Container[T’]应该被看做是Container[T]的父类。这初看起来是非怪异并不符合情理的。在列举它的适用场景之前,我们同样来先看一下解释逆变的例子:
scala> class Contravariant[-A]
defined class Contravariant
scala> val cv: Contravariant[String] = new Contravariant[AnyRef]
cv: Contravariant[AnyRef] = Contravariant@49fa7ba
scala> val fail: Contravariant[AnyRef] = new Contravariant[String]
<console>:6: error: type mismatch;
found : Contravariant[String]
required: Contravariant[AnyRef]
val fail: Contravariant[AnyRef] = new Contravariant[String]
^
很显然,上述例子中AnyRef是String的父类,但是Contravariant[AnyRef]则变成了Contravariant[String]的子类,从而可以进行赋值操作(变量声明为父类型,引用的是子类型实例)
逆变初看上去有些离经叛道,对于逆变的合理性,本系列有一篇专门的文章: Scala之“逆变”合理性的思考 ,对逆变进行了深入的思考。
在引入了范型之后,有时候我们需要对接受类型的范围进行一些限制,特别在引入了协变和逆变之后,更需要对于协变的下界和逆变的上界进行界定。
表述形式: U >: T
这种表述的含义是:U必须是类型T或T的父类。>:有点类似>=的意思。
如前所属,协变往往需要用到下界,例如:
scala> class Consumer[+T](t: T) {
| def f1(t: T) = {}
| }
<console>:12: error: covariant type T occurs in contravariant position in type T of value t
def f1(t: T) = {}
^
scala> class Consumer[+T](t: T) {
| def f1[U >: T](u : U) = {println(u)}
| }
defined class Consumer
上述示例中Consumer是伴随类型T协变的,而f1的类型是Function1[-T1,+R],即要求类型T是逆变的,这样函数f1和类Consumer对类型T的要求就产生了冲突,比如Consumer可以声明使用T的一个子类型,而这个子类型是不能适用到f1上的。为了解决这个问题,我们可以针对f1的类型参数进行下界控制。
Scala官方文档中也有一个下界的使用示例:http://docs.scala-lang.org/tutorials/tour/lower-type-bounds
表述形式: S <: T
这种表述的含义是:S必须是类型T或T的子类。<:有点类似<=的意思。
Scala官方文档中有一个上界的使用示例:
trait Similar {
def isSimilar(x: Any): Boolean
}
case class MyInt(x: Int) extends Similar {
def isSimilar(m: Any): Boolean =
m.isInstanceOf[MyInt] &&
m.asInstanceOf[MyInt].x == x
}
object UpperBoundTest extends App {
def findSimilar[T <: Similar](e: T, xs: List[T]): Boolean =
if (xs.isEmpty) false
else if (e.isSimilar(xs.head)) true
else findSimilar[T](e, xs.tail)
val list: List[MyInt] = List(MyInt(1), MyInt(2), MyInt(3))
println(findSimilar[MyInt](MyInt(4), list))
println(findSimilar[MyInt](MyInt(2), list))
}
在这个例子中我们注意第12行,如果没有上界声明:T <: Similar
,被声明为T的实例e是不能调用isSimilar方法的。
表述形式: A <% B
这种表述的含义是:类型A必须“可被视为”类型B,“可被视为”的意思就是存在一个将类A转换为类型B的隐式转换!下面这段示意代码演示的就是把类型A的实例当作了类型B去使用,因为存在一个从A到B的隐式转换。
def f[A <% B](a: A) = a.bMethod
而实际上,上述代码会被编译为:
def f[A](a: A)(implicit ev: A => B) = a.bMethod
在scala的类库中有这样典型的应用:
def f[A <% Ordered[A]](a: A, b: A) = if (a < b) a else b
对于任何可以隐式转换为Ordered[A]类型的类型A,都可以直接使用比较大小的方法<。
注:Scala社区已不再推荐使用视界: https://github.com/scala/scala/pull/2909
让我们通过如下一段示例代码来了解一下Context Bound:
import math.Ordering
case class MyList[A](list: List[A]) {
def sortBy1[B](f: A => B)(implicit ord: Ordering[B]): List[A] = list.sortBy(f)(ord)
def sortBy2[B : Ordering](f: A => B): List[A] = list.sortBy(f)(implicitly[Ordering[B]])
}
val list = MyList(List(1,3,5,2,4))
list sortBy1 (i => -i)
list sortBy2 (i => -i)
sortBy1和sortBy2的效果是一样的,即针对一个集合,依据一个函数,把集合中的每个元素转换成另外一种类型的值,然后根据转化后的类型的排序规则( Ordering[B]的一个实例)进行排序。sortBy1是一种直白写法,而sortBy2是一种从语法上更为简洁的实现,或者说sortBy2实际上会被编译成sortBy1的形式。
简单地说,当我们看到一个Context Bound时,有两点我们应该立刻想到,以这里的[B : Ordering]为例:
每当我们使用到Context Bound时,我们都可以通过上述的转换来理解Context Bound。当然,当你已经非常了解这中间发生的故事之后,你会就会适应这种简洁的语法而不再需要在脑袋里进行这种转换转换。
关于View Bound和Context Bound,可以参考Scala官方文档的一篇文章: http://docs.scala-lang.org/tutorials/FAQ/context-and-view-bounds.html
参考:
https://twitter.github.io/scala_school/zh_cn/type-basics.html
http://www.spoofer.top/2016/03/12/scala-%E5%8F%98%E4%B8%8E%E7%95%8C