我在理解方法参数中限制的协方差类型时感到困惑。我通读了很多材料,但我无法理解以下概念。
class SomeThing[+T] {
def method(a:T) = {...} <-- produces error
}
在上面的代码中,a是T类型。为什么我们不能传递T的子类型?方法对参数x的所有期望,都可以由T的子类型完美地实现。
类似地,当我们有反变量类型T(-T)时,它不能作为方法参数传递;但这是允许的。我认为它不能被传递的原因是:例如,say方法调用一个方法(存在于对象a中)在上存在于T中的。当我们通过T的超类型时,它可能不存在。但它是编译器允许的。这让我很困惑。
class SomeThing[-T] {
def method(a:T) = {...} <-- allowed
}
因此,通过查看上面的内容,它是协变的,应该在方法参数和返回类型中都允许。无法应用反变体。
有人能帮我理解吗。
方差的关键是它会影响类从外部看的样子。
协方差表示SomeThing[Int]
的实例可以被视为SomeThing[AnyVal]
的实例,因为AnyVal
是Int
的超类。
在这种情况下,您的方法
def method(a: Int)
将成为
def method(a: AnyVal)
这显然是一个问题,因为您现在可以将Double
传递给只接受Int
值的SomeThing[Int]
方法。请记住,实际对象不会改变,只会改变类型系统感知它的方式。
相反说SomeThing[AnyVal]
可以被视为SomeThing[Int]
,所以
def method(a: AnyVal)
成为
def method(a: Int)
这是可以的,因为您总是可以在需要AnyVal
的地方传递Int
。
如果你遵循返回类型的逻辑,你会发现它的工作方式正好相反。返回协变类型是可以的,因为它们总是可以被视为超类类型。不能返回逆变类型,因为返回类型可能是实际类型的子类型,这是无法保证的。
我认为您在向后攻击这个问题。如果T
是协变的,则不能将a:T
作为方法的自变量,这是一个约束,因为否则一些不合逻辑的代码将是完全有效的
class A
class B extends A
class C extends B
val myBThing = new SomeThing[B]
这里,myBThing.method
接受一个B
,您是对的,我们可以传递任何扩展B
的内容,所以myBThing.method(new C)
完全可以。然而,myBThing.method(new A)
不是!
既然我们已经用协变定义了SomeThing
,我也可以写这个
val myAThing: SomeThing[A] = myBThing // Valid since B <: A entails SomeThing[B] <: Something[A] by definition of covariance
myAThing.method(new A) // What? You're managing to send an A to a method that was implemented to receives B and subtypes!
现在,您可以看到为什么我们施加不将T
作为参数传递的约束(参数处于"逆变位置")。
我们可以对收益头寸的反方差进行类似的论证。请记住,逆变意味着B <: A
包含`SomeThing[A]<:某事[B]`。
假设您正在定义以下
class A
class B extends A
class SomeThingA[-T](val value: T) // Compiler won't like T in a return type like myThing.value
// If the class definition compiled, we could write
val myThingA: SomeThing[A] = new SomeThing(new A)
val someA: A = myThingA.value
val myThingB: SomeThing[B] = myThingA // Valid because T contravariant
val someB: B = myThingB.value // What? I only ever stored an A!
有关更多详细信息,请参阅此答案。
在class SomeThing[T]
的情况下,在T
之前放置+
或-
实际上对类本身的影响大于对类型参数的影响。
考虑以下内容:
val instanceA = new SomeThing[A]
val instanceB = new SomeThing[B]
如果SomeThing
在T
上是不变的(没有+
或-
),则实例将不具有方差关系。
如果SomeThing
在T
([+T]
)上是协变的,则实例将具有与A
和B
相同的方差关系。换句话说,如果A
是B
的子类型(反之亦然),则实例将反映相同的关系。
如果SomeThing
与T
([-T]
)相反,则实例将具有与A
和B
相反的方差关系。换句话说,如果A
是B
的子类型,则instanceB
将是instanceA
的子类型。
但是方差指标确实影响了类型参数的使用方式。如果T
被标记为+
,则它不能被放置在反变位置,同样,如果被标记为-
,则它不能够被放置在协变位置。我们在定义方法时经常遇到这种情况。
Scala方法与Scala函数特性密切相关:Function0
、Function1
、Function2
等。
考虑Function1
:的定义
trait Function1[-T1, +R] extends AnyRef
现在假设您想要传递一个这种类型的函数。
def useThisFunc(f: A => B):Unit = {...}
因为Function1
在其接收参数上是逆变的,在其结果上是协变的,所以以下所有参数都可以作为useThisFunc()
参数接受。
val a2b : A => B = ???
val supa2b : SuperOfA => B = ???
val a2subb : A => SubOfB = ???
val supa2subb : SuperOfA => SubOfB = ???
因此,总之,如果SomeThing
在T
上是协变的,那么就不能将T
作为成员方法的传递参数,因为FunctionX
在其参数类型上是逆变的。同样,如果SomeThing
与T
相反,则不能将T
作为成员方法返回类型,因为FunctionX
的返回类型是协变的。