Scala概述(五)抽象(1.3、1.4)

二元操作和参数下界(Binary methods and lower bounds迄今为止,我们一直将协变性与不可变数据结构联系在一起,然而由于二元操作(Binary methods,就是指一个对象的方法,其参数类型也是这个对象类型,例如:x+y这种——译注)的存在,这种做法并不完全正确。例如,为GenList类增加一个prepend(前追加)方法,最自然的做法是将其定义成为接收一个相应的list元素类型参数:

abstract class GenList[+T] { ...

def prepend(x: T): GenList[T] = // illegal!

new Cons(x, this)

}

可惜这样做会导致类型错误因为这种定义使得TGenList中处于逆协变的位置从而不能标记为协变参数(+T)。这一点非常遗憾,因为从概念上说不可变的list对于其元素类型而言应该是协变的,不过这个问题可以通过参数下界对prepend方法进行泛化而解决:

abstract class GenList[+T] { ...

def prepend[S >: T](x: S): GenList[S] = // OK

new Cons(x, this)

}

这里prepend是一个多态方法,接收T的某个父类型S作为参数,返回元素类型为Slist。这个定义是合法的,因为参数下界被归类为协变位置,从而TGenList中只出现在协变位置上。

与通配符模式相比较(Comparison with wildcardsJava 5.0中可以提供一种通过通配符标记协变性的方法[45],这种模式本质上是IgarashiViroli提出的可变类型参数[26]的一种语法变体。与Scala不同的是,Java 5.0的标注是针对类型表达式而不是类型定义。例如:在每一个需要用到协变的generic list的时候,都将其声明为GenList extends T>,这是一个类型表达式,表示其所声明的对象实例的所有元素都是T的子类型。

协变通配符可以用于任何类型表达式当中,但是要注意,出现在非协变的位置上的类型成员将会被忽略(forgotten),这对于保证类型的正确性是必须的。例如:GenCellextends Number>类型只有那个get方法(返回Number类型)才有效,而其set方法,由于其类型参数是逆协变的,会被忽略。

Scala的早期版本中,我们也实验过在调用时标注协变性的方式,类似于通配符。初看之下,这种方式有很大的灵活性,因为一个类型的成员既可以是协变的,也可以是非协变的,用户可以根据情况选择是不是使用通配符。但是,这种灵活性也是有代价的,因为这样作要有用户而不是设计者来保证对协变性的使用是一致的。在实践中我们发现,调用时标注协变性的方式很难保证一致性,经常会出现类型错误。相反,定义时标注协变性对于正确地设计类型有很大帮助,例如可以很好地指导人们设计方法时知道哪些应当使用参数下界。另外,Scalamixin构成(见第六节)可以让人很容易将一个类分成协变的和非协变的部分,而在Java这种单根结构+接口的继承模型中,这样做是非常麻烦的。因此,Scala的新版本将标注协变性的方式从使用时标注改为了定义时标注。

你可能感兴趣的:(干将莫邪)