内容简介:这个语法应该是Scala最常用的语法之一。它比C#的类型约束走的更远,因为它既可以表示上界,也可以表示下界。不过在此之前需要搞清几个概念。我们知道Java允许数组协变,这会导致将一个子类添加到父类数组时的异常:从Scala一切皆方法的角度,就是数组的
这个语法应该是Scala最常用的语法之一。它比C#的类型约束走的更远,因为它既可以表示上界,也可以表示下界。不过在此之前需要搞清几个概念。
Variance Position
我们知道 Java 允许数组协变,这会导致将一个子类添加到父类数组时的异常:
String[] a1 = { "abc" }; Object[] a2 = a1; a2[0] = new Integer(17); String s = a1[0]; 复制代码
从Scala一切皆方法的角度,就是数组的 update
方法的参数是一个类型参数。更一般的来看,这里 update
的参数是一个抗变点(contravariant position),或者说负向点(negative position)。其实我觉得负向点的说法更好,因为它可以与型变的概念区分开。一个抗变点不允许接受协变的类型参数。另外,还有2种型变点:协变点(covariant position),不变点(novariant position)。
由于类型参数本身也是一个类型,因此泛型是可以嵌套的。这给最终验证型变注解带来了一定的复杂性。在语言规范里是这样说明的:
The top-level of the type or template is always in covariant position. The variance position changes at the following constructs.
- The variance position of a method parameter is the opposite of the variance position of the enclosing parameter clause.
- The variance position of a type parameter is the opposite of the variance position of the enclosing type parameter clause.
- The variance position of the lower bound of a type declaration or type parameter is the opposite of the variance position of the type declaration or parameter.
这里型变发生了翻转(flipped)。一个函数参数的型变点会翻转,同时类型参数(如果有下界或者型变注解)的型变点也会翻转。这是 Programming in Scala 中构造的示例:
abstract class Cat[-T, +U] { def meow[W−](volume: T−, listener: Cat[U+, T−]−) : Cat[Cat[U+, T−]−, U+]+ } 复制代码
注意到这里 Cat[U, T]
的U和T互换了位置,因为他们所在的型变点发生了翻转。比如,对于返回值而言,因为最外层的Cat的第一个类型参数是抗变的,所以U翻转为协变点,T为抗变点(规则2)。同样listener也发生了翻转(规则1)。
原理上, 方法的参数是逆变点,而返回值是协变点 。这主要是基于里氏替换原则的推理。举个例子:
abstract class Box[+F <: Fruit] { def fruit: F def contains(aFruit: Fruit) = fruit.name.equals(aFruit.name) } class AppleBox(apple: Apple) extends Box[Apple] { def fruit = apple } var fruitBox: Box[Fruit] = new AppleBox(new Apple) var fruit: Fruit = fruitBox.fruit 复制代码
这里的F是典型的协变应用。不过,假设这里F是抗变的,那么就会出现问题,因为作为父类的AppleBox需要返回一个apple,而子类fruitBox并不能替换它。
最后,Scala默认的行为是不变(novariant)。这也是一个更符合逻辑的抉择。
Lower Bound
下界既可以是一个具体的类,也可以是一个类型参数。还是用 Programming in Scala 的例子,它通过下界允许对一个协变类调用包含逆变点的方法:
class Queue[+T] (private val leading: List[T], private val trailing: List[T] ) { ... def append[U >: T](x: U) = new Queue[U](leading, x :: trailing) } 复制代码
这里可以将一个 Orange
追加到 Queue[Apple]
上得到一个 Queue[Fruit]
。注意到当作为 Queue[Fruit]
时,U必须是 Fruit
的超类,因此这个下界防止了前面Java代码中的赋值错误。
更一般的来说,这里的下界定义了一次翻转(规则3)。这其实很好理解:我们总是在父类中调用方法 append
,而T是U的子类。
Programming in Scala 在这里提到了术语声明处型变(declaration-site variance)与使用处型变(use-site variance)。这里的site是声明型变的位置。Scala是声明处型变的,这一点和C#相同。Java是使用处型变的,这种风格要求 程序员 必须很清楚类的可变性,缺少了编译器的支持。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 最好的Julia语言
- python是世界上最好的语言
- “Python才是世界上最好的语言”
- 我才是世界上最好的编程语言
- “最好的语言“ 25 岁了,PHP说要走向安全和开放!
- PHP 是世界上最好的语言?黑客偏爱用 Python
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
C++Primer Plus
Stephen Prata、孙建春、韦强 / 孙建春、韦强 / 人民邮电出版社 / 2005-5 / 72.00元
C++ Primer Plus(第五版)中文版,ISBN:9787115134165,作者:(美)Stephen Prata著;孙建春,韦强译一起来看看 《C++Primer Plus》 这本书的介绍吧!