考虑以下两个代码。他们实现了相同的目标:只有A[T]
- s可以存储在Container
T
扩展C
然而,他们使用两种不同的方法来实现这一目标:
1)存在感
2)协方差
我更喜欢第一种解决方案,因为A
仍然更简单。我有什么理由想要使用第二种解决方案(协方差)吗?
我的第二个解决方案的问题是它不自然,因为它不应该是A
- 有责任描述我可以存储在Container中的内容而不是那应该是Container的责任。第二个解决方案也是更复杂,一旦我想开始对A
进行操作,那么我必须处理协方差带来的所有内容。
使用第二种(更复杂,更不自然)解决方案可以获得什么好处?
object Existentials extends App {
class A[T](var t:T)
class C
class C1 extends C
class C2 extends C
class Z
class Container[T]{
var t:T = _
}
val c=new Container[A[_<:C]]()
c.t=new A(new C)
// c.t=new Z // not compile
val r: A[_ <: C] = c.t
println(r)
}
object Cov extends App{
class A[+T](val t:T)
class C
class C1 extends C
class C2 extends C
class Z
class Container[T]{
var t:T = _
}
val c: Container[A[C]] =new Container[A[C]]()
c.t=new A(new C)
//c.t=new A(new Z) // not compile
val r: A[C] = c.t
println(r)
}
编辑(回应Alexey的回答):
评论: “我对第二种解决方案的问题在于,在描述我可以存储在Container中的东西不应该是A-s的责任并不是自然的,不应该是容器的责任。”
如果我有class A[T](var t:T)
这意味着我只能在容器中的任何容器中存储A[T]
- s而不能存储A[S]
S<:T
class A[+T](var t:T)
}。
但是,如果我有A[S]
,那么我可以S<:T
将A
存储在任何容器中。
因此,当声明A
要么是不变量还是协变量时,我决定在容器中存储哪种类型的A [S](如上所示),这个决定发生在{{1}的声明中}。
但是,我认为,这个决定应该在容器的声明中进行,因为它是容器特定的,允许进入该容器,只有A[T]
- s或{{1其中A[S]
- s。
换句话说,更改S<:T
中的差异会全局影响,而将容器的类型参数从A[T]
更改为A[T]
会对容器本身产生明确定义的局部影响。因此,“改变应具有局部效应”的原则也有利于存在性解决方案。
答案 0 :(得分:4)
在第一种情况下A
更简单,但在第二种情况下,它的客户端是。由于您通常使用A
的地方不止一个,因此这通常是值得的权衡。您自己的代码演示了它:当您需要在第一种情况下(在两个地方)编写A[_ <: C]
时,您可以在第二种情况下使用A[C]
。
此外,在第一种情况下,您可以只写A[C]
,其中A[_ <: C]
是真正需要的。假设你有一个方法
def foo(x: A[C]): C = x.t
现在,您无法使用foo(y)
致电y: A[C1]
,即使它有意义:y.t
的类型为C
。
当您的代码中发生这种情况时,它可以修复,但第三方呢?
当然,这也适用于标准库类型:如果Maybe
和List
之类的类型不是协变的,那么所有采用/返回它们的方法的签名都必须更加复杂或许多目前有效并且完美无缺的程序会破裂。
描述我可以存储在Container中的内容不应该是A-s的责任,而不应该是Container的责任。
方差不是关于你可以存放在容器中的东西;它是关于A[B]
是A[C]
的子类型的时间。这个论点有点像说你根本不应该extends
:否则class Apple extends Fruit
允许你在Apple
中存储Container[Fruit]
,并确定它是Container
1}}的责任。