我对通用子类型感到困惑。
在Java中,如果类型A
是B
的子类型,则泛型类型C<A>
和C<B>
是不变的。例如,ArrayList<Base>
不是ArrayList<Derived>
的子类型。
但是,在Scala中,如果类型C<A>
是C<B>
的子类型,则通用类型A
和B
是协变的。那么Scala中泛型类的属性是什么,而Java中却没有?
答案 0 :(得分:41)
首先请注意,方差是通用类型参数的属性,而不是参数化类型本身的属性。
其次:scala是错误的 - 默认情况下,类型参数是不变的。让我们调查吧!
Java有使用网站差异注释。也就是说,你可以声明这样的方法:
boolean addAll(Collection<? extends T> c);
然而,有一种形式的“参数化类型”(我使用松散的术语),其中类型参数是协变的:Java Arrays! (这实际上是疯狂的,因为数组是可变的,因此很容易绕过类型系统)。请考虑以下事项:
public static void subvert(Object[] arr) { arr[0] = "Oh Noes!"; }
然后:
Integer[] arr = new Integer[1];
subvert(arr); //this call is allowed as arrays are covariant
Integer i = arr[0];
这是一个很好的面试问题:会发生什么?
在Scala中,您有声明网站差异。也就是说,类型参数的方差与参数一起声明(使用注释+
和-
):
trait Function1[-I, +O]
这表示特征Function1
有两个类型参数I
和O
,它们分别是反变量和共变量。 如果未声明+/-
注释,则类型参数不变。例如,Set在其类型参数中是不变的:
scala> def foo(set: Set[Any]) = ()
foo: (set: Set[Any])Unit
scala> Set(1)
res4: scala.collection.immutable.Set[Int] = Set(1)
scala> foo(res4)
<console>:10: error: type mismatch;
found : scala.collection.immutable.Set[Int]
required: Set[Any]
Note: Int <: Any, but trait Set is invariant in type A.
You may wish to investigate a wildcard type such as `_ <: Any`. (SLS 3.2.10)
foo(res4)
^
然而,列表被声明为协变:
scala> def bar(list: List[Any]) = ()
bar: (list: List[Any])Unit
scala> List(1)
res6: List[Int] = List(1)
scala> bar(res6)
证明这一点的另一种方法是直接向编译器询问子类型证据:
scala> class Cov[+A]
defined class Cov
scala> implicitly[Cov[Int] <:< Cov[Any]]
res8: <:<[Cov[Int],Cov[Any]] = <function1>
但是使用不变的类型参数
scala> class Inv[A]
defined class Inv
scala> implicitly[Inv[Int] <:< Inv[Any]]
<console>:9: error: Cannot prove that Inv[Int] <:< Inv[Any].
implicitly[Inv[Int] <:< Inv[Any]]
^
最后,逆转:
scala> class Con[-A]
defined class Con
scala> implicitly[Con[Any] <:< Con[Int]]
res10: <:<[Con[Any],Con[Int]] = <function1>