我正在阅读http://oldfashionedsoftware.com/2008/08/26/variance-basics-in-java-and-scala/
正在查看代码
class CoVar[+T](param1: T) {
def method1(param2: T) = { }
def method2: T = { param1 }
def method3: List[T] = { List[T](param1) }
def method4[U >: T]: List[U] = { List[U](param1) }
val val1: T = method2
val val2: Any = param1
var var1: T = method2
var var2: Any = param1
}
那么如果我有一个
val covar1 = new CoVar(new Car)
val covar2: CoVar[Vehicle] = covar1 //completely legal with covariant
现在,让我们来看看方法
我认为这对于method1(param:Vehicle)来说还可以,因为我可以传递一辆新的车辆就好或者新车很好
但是原始的CoVar类没有编译,因为它说method1是逆变位置。我认为逆变意味着我可以通过
现在,用ContraVar和方法1再次讨论这个问题
class ContraVar[-T](param1: T) {
def method1(param2: T) = { }
val val2: Any = param1
var var2: Any = param1
}
val temp1 = new ContraVar(new Car)
val temp2: ContraVar[Ford] = temp1
temp2.method1(new Ford)
temp2.method1(new FordMustang)
temp2.method1(new Car) //fails to compile(good)
哪个工作得很好。有人可以解释为什么method1在CoVar上打破了吗?也许我正在走向完全错误的道路,因为让method1编译得很好会出错?
感谢, 迪安
答案 0 :(得分:2)
您的问题归结为以下为何非法
trait Tool[+A] {
def treat(c: A): Unit
}
想象一下它会编译......让我们从外面看一个用例:
def apply[A](tool: Tool[A], car: A): Unit = tool.treat(car)
假设A
有两种可能的类型:
trait Car
trait Mustang extends Car { def awe(): Unit }
协方差意味着Tool[Mustang] <: Tool[Car]
。因此,每当要求Tool[Car]
时,您都可以使用Tool[Mustang]
。现在想象一下Tool[Mustang]
:
val tm = new Tool[Mustang] { def treat(m: Mustang) = m.awe() }
现在你可以致电:
apply[Car](tm, new Car {})
这意味着,tm
可以访问通用汽车中不存在的方法awe
。显然这不是一种声音类型的关系。 因此,只要在参数位置使用类型,它就必须是不变的或逆变的。
答案 1 :(得分:1)
让我们给你的方法1一个身体:
class CoVar[+T] {
var listOfT: List[T] = Nil
// method1 prepends the given element to listOfT
def method1(param2: T) = { listOfT = param2 :: ListOfT }
}
现在我们实际上对传递给method1的参数做了一些事情,如果允许的话,很容易看出会出现什么问题。
// Lets construct one of these that holds Ints. and add something to it
val covarInt = new CoVar[Int]
covarInt.method1(1)
// lets assign to a more general value, this is no problem because of the covariance
val covarAny: CoVar[Any] = covarInt
// now lets do a bad thing:
covarAny.method1("this is a string not an Int")
最后一行显示了允许的坏事。由于covarAny
是CoVar[Any]
类型,这意味着在CoVar
类中type T = Any
,因此预期作为method1
输入的类型为Any
,因此String
是String
,因此应该允许将Any
传递给此函数。然而,方法体将尝试将我们传递的String
添加到Int
的列表中,这不应该被允许。
答案 2 :(得分:0)
我有一个新的答案,我认为帮助我很多为什么params是逆变而不是协变。这个例子也可以在没有泛型的情况下完成。
让我们使用函数对象来定义Animal,Bird和Duck类
class Animal {
def makeSound() = "animalsound"
def walk()
}
class Bird extends Animal {
def makeSound() = "tweeeeet"
def fly()
}
class Duck extends Bird {
def makeSound() = "quack"
def paddle()
}
现在,让我们尝试定义一个协变函数
val doSomething: (Bird => String) = { d:Duck => d.paddle() }
当然,当一只小鸟被传入时,我们不能施放给鸭子,因为它可能不是鸭子,而且在鸟类中它肯定不会用桨法进行划桨。
现在,让我们尝试定义一个逆变函数
val doSomething: (Bird => String) = { a:Animal => a.walk() }
这现在编译并且有效,因为鸟是动物所以它将有一个步行方法。这对我来说真的很清楚,自然方法与函数非常相似,或者你总是可以将方法转换为函数,在这种情况下,它需要对params进行逆变。
现在,还有一种方法我们可以在这里做一些事情,如T&gt;:鸟可能吗?我想知道我是否要做一个(T&gt;:Bird =&gt; String)= .....
好吧,我认为我现在对此并不了解,并将其作为TODO留在我要了解的事项清单上。