泛型:
Scala中,泛型定义方式和Java类似,如下:
class className[T] (params:){...}
其中T为类型参数
比如:
scala> class Basket[T]
defined class Basket
可变性:
假如Apple是Fruit的子类(Apple <: Fruit),那么Basket[Fruit]和Basket[Apple]之间会不会存在父子关系?
scala> class Fruit(val name:String, val color:String){
| def getName():String = name
| def getColor():String = color
| override def toString():String = color + "\t" + name
| }
defined class Fruit
scala> class Apple extends Fruit("Apple","Red")
defined class Apple
scala> val fruitBasket : Basket[Fruit] = new Basket[Apple]()
:10: error: type mismatch;
found : Basket[Apple]
required: Basket[Fruit]
Note: Apple <: Fruit, but class Basket is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
val fruitBasket : Basket[Fruit] = new Basket[Apple]()
^
从上面代码的运行结果可以看出,Basket[T]的实例Basket[Apple]并不能作为Basket[Fruit]的子类。也就是,scala中,对于泛型类G[T],以及具有超类子类关系的类型A >: B (A是B的超类),G[A]和G[B]之间并没有父子关系。如果想让G[A]和G[B]之间具有父子关系,必须用到可变性注解。G[+T]表示类型参数T是协变的(covariant),对于B <: A,G[B]是G [A]的子类型(即 G[B] <: G[A]);G[-T]表示类型参数T是逆变的(contravariant),对于A >: B,G[A]是G[B]的子类型(即G[A] <: G[B]);默认状况下G[T]表示类型参数T是不可变的(invariant),对于A >: B,G[A]和G[B]没有关系。
示例如下:
scala> class Basket[+T]
defined class Basket
scala> val fruitBasket:Basket[Fruit] = new Basket[Apple]()
fruitBasket: Basket[Fruit] = Basket@32e16d62
scala> class Basket[-T]
defined class Basket
scala> val appleBasket:Basket[Apple] = new Basket[Fruit]()
appleBasket: Basket[Apple] = Basket@3df122c1
可变性位置:
现在在Basket中添加函数:
scala> class Basket[+T]{
| var num:Int = 0
| def put(item:T) = {num += 1}
| }
:9: error: covariant type T occurs in contravariant position in type T of value item
def put(item:T) = {num += 1}
^
添加函数失败,错误的原因也很容易分析:
假如上述代码编译通过,由于参数T是协变的,对于:
val basket:Basket[Fruit] = new Basket[Apple]()
basket.put(new Fruit('Name',"Color"))
代码的第一行没有问题,但是运行到第二行,由于basket其实是Basket[Applet]类型,只接受类型为Apple的参数,所以会抛异常;而对于类型Basket[Fruit]来讲,函数put可以接收类型为Fruit的参数。所以上面的定义是有问题的。
为了解决引入可变性类型导致的问题,Scala中引入了可变性位置。对于协变型类型参数+T(类型前用+修饰),只能出现在协变位置;对于逆变型类型参数-T(类型前用-修饰),只能出现在逆变位置;对于不可变型参数T(类型前没有修饰),只能出现在不可变位置。
逆变的对立面是协变;协变的对立面是逆变;不可变的对立面还是不可变。Scala中,类或者类型的最外层总是处于协变位置;可变性位置会在下面几种状况下发生改变:
1.方法参数的可变性位置是包含该参数的代码块的对立面;
2.方法类型参数的可变性位置是包含该类型参数的代码块的对立面;
3.类型声明或者类型参数的下限的可变性是该类型或者类型参数的对立面;
4.可变变量总是处于不可变位置;
5.type 别名表达式的右侧总是处于不可变位置;
6.对于类型选取S#T的前缀S中处于不可变位置;
7.对于类型 S[. . .T . . . ]的类型参数T:如果对应的类型S[. . .T . . . ]是不可变的,那么T处于不可变位置;如果对应的类型参数是逆变的,那么T的可变性是类型S[. . .T . . . ]的对立面。
lower bound、upper bound的定义如下:
对于 A >: B, B是A的lower bound
对于 A <: B, B是A的upper bound
为了让上面的代码编译通过,可以引入lower type bound:
scala> class Basket[+T]{
| var num:Int = 0
| def put[B >: T](item:B) = {num += 1}
| }
defined class Basket
由规则2可知,put[B >:T ]中,B的可变性位置为-,根据规则3知道T的可变性位置为+,所以T可以出现在put的类型参数中;同是对于任意类型
T, T <: T 并且 T >: T,所以类型B可以出现在put函数的参数(item:B)中。
同理,T如果是逆变的,则类型参数可以直接使用T:
scala> class Basket[-T]{
| var num:Int = 0
| def put[T](item:T) = {num += 1}
| }
defined class Basket
但是,如果逆变类型作为函数的返回值,编译器就会报错:
scala> abstract class Basket[-T]{
| def take():T
| }
:8: error: contravariant type T occurs in covariant position in type ()T of method take
def take():T
^
由于函数的返回值的可变性位置是协变的,所以逆变的T作为返回值的时候就会报错。为了解决这个问题,可以引入upper type bound:
scala> abstract class Basket[-T]{
| def take[B <:T]():B
| }
defined class Basket
由于类型参数B本身也是协变的,所以可以作为函数的返回值。
Scala中,比较特殊的一点是,对于对象私有(private[this])的属性或者方法,不对其可变性进行检查。
从上面的例子可以看出:函数的参数是不可变或者逆变的,函数的返回值是不可变或者协变的。
参考自:
Programming scala 2nd edition
http://www.scala-lang.org/docu/files/ScalaReference.pdf
阅读(1803) | 评论(0) | 转发(0) |