对逆变的概念可以参考本系列的前1篇文章: Scala之类型参数化:Type Parameterization 本文的重点是要解释“逆变”的公道性。本文原文出处: http://blog.csdn.net/bluishglc/article/details/52585991 严禁任何情势的转载,否则将拜托CSDN官方保护权益!
在思考“逆变”的公道性这个问题上,我们需要清晰地认识到1个条件,即父类与子类之间的关系实质,我们说:如果类A是类B的父类,那末所有出现类A声明的地方,我们都可使用类B的实例进行替换,或说所有适用于类A的操作一样适用于类B,简言之就是子类型可以透明无害地替换父类型(也就是里氏替换原则),由于子类型1定也是父类型,但父类型未必1定是子类型(有其他子类型),上述原则就是大家所熟知的里氏替换原则。
Liskov Substitution Principle (里氏替换原则)
It is safe to assume that a type T is a subtype of a type U if you can substitute a value of type T wherever a value of type U is required.
The principle holds if T supports the same operations as U and all of T’s operations require less and provide more than the corresponding operations in U.
在回顾完上述表述以后,我们来重新审视1下“逆变”存在的公道性。首先定义以下1个Animal类族:
scala> class Animal
defined class Animal
scala> class Bird extends Animal
defined class Bird
scala> class Dog extends Animal
defined class Dog
现在有f1,f2两个函数:
def f1(x: Bird): Unit // instance of Function1[Bird, Unit]
def f2(x: Animal): Unit // instance of Function1[Animal, Unit]
在这里,f1是f2的父类。为何?我们知道,Function1的类型声明是Function1[-T1,+R],即函数是虽参数类型逆变,返回值类型协变的。其中随返回值类型协变是很容易理解的,随参数类型逆变常常让人费解,对此,我们一样使用前面提到的原则进行判定:父类可以被子类替换,反之则不可以,但是这里的情况会略微有些复杂,由于我们要判断的是函数类型之间的可替换关系(即父子关系),我们可以认为函数是1种“复合”类型,它们的类型是由它们的参数和返回值的类型决定的,因此我们可以很自然的延展出这样1个规则:对具有相同参数列表类型和返回值类型的函数,如果传给函数1的参数类型一样可以传给函数2,而传给函数2的参数未必都能传给函数1,也就是说,只从参数部份考量,函数1可以被函数2替换,即函数1是父类,函数2 是子类。
对f2,我们说传给它1个Animal实例它可以工作,传给它1个Bird实例它依然可以工作,在传给它1个Bird实例时,我们就要注意到,这时候的f2(仅看参数部份)实例的类型实际上就已变成f1了,这时候所有声明使用f1类型的地方都可以用f2的实例去替换,但是反过来,所有声明了使用f2类型的地方我们是不能用f1的实例去替换的,由于对f2来讲,它可以接受Animal类型的任何其他子类型,比如Dog,但是Dog类型明显不适用于f1的。所以总结起来,f1可以被f2替换,但是f2不能被f1替换,所以f1是f2的父类型!
让我们再延伸地思考1下,我们可以说:由于f2是“消费”(consume)1个较为“通用”的父类型,这使得函数f2本身自然地能接纳和处理给定参数类型的所有子类型,也就意味着f2可以去替换或赋值给那些所有声明使用“具体”子类型为参数的函数,比如f1, 所以f1是父类,f2是子类!这类“消费”关系决定了逆变存在的理由,可以表述为PECS原理:
PECS stands for producer-extends, consumer-super.
In other words, if a parameterized type represents a T producer, use <? extends T>;
if it represents a T consumer, use <? super T>.
上述PECS原则换1种方法表述为:
G[+A]类似1个生产者,提供数据。(大部份情况下称G为容器类型)
G[-A] 是1个消费者,主要用来消费数据。(参考垃圾桶和垃圾的例子)
虽然我们仍然在使用里氏替换原则来分析和辨认“逆变”的场景,但是我们不能不承认这类解释仍然只是1种逻辑上的逆推,它的解释总是让人觉得不是那末“解痒”,在本文的最后,我试图从正面给出1种“逆变”公道性的解释:
**我们说在现实世界里,如果有1类物品专门针对另外一类物品而存在,除人们1般认为的伴随着被处理物品的细化,处理品本身需要不断地跟进细化,这是“协变”的场景,也确切有可能会存在另外1种完全相反的情形:即伴随着被处理物品的细化,在掌握了愈来愈多处被理物品的信息和特点的趋势下,处理物品本身却可以变的愈发的简单(处理面变窄),反倒是那些处理更通用物品的处理类复杂的多,由于它们要斟酌的可能的情况更多更复杂,那末这类情形就是典型的“逆变”!
1个典型是例子是空调和遥控器,如果说遥控器是基于空调类型的范型类,那末它天然应当是逆变的,即:RemoteController[-T], 空调品牌和型号越细化,遥控器实际上越单1,实现起来也越简单,反倒是随着空调类型不断地向上抽象,遥控器会变得越加复杂,直到面向所有空调通用的遥控器RemoteController[AirConditioner]诞生,这也就是我们见到过的那种万能遥控器。万能遥控器可以替换任何品牌和型号的遥控器,因此它是它们的子类!
我们可以看到大多数的逆变类有以下1些特点: