这个问题有一个非常简单的原因,实际上与方差没有任何关系。考虑更简单的例子:
object Example {
def gimmeAListOf[T]: List[T] = List[Int](10)
}
此代码段捕获了您的代码的主要思想。但这是不正确的:
val list = Example.gimmeAListOf[String]
将是什么类型的list?我们专门询问gimmeAListOf了 method List[String],但是它总是返回List[Int](10)。显然,这是一个错误。
因此,简而言之,当该方法具有method[T]: Example[T]真正声明的签名时:“对于您给我的任何类型T,我将返回一个”的实例Example[T]。这种类型有时被称为“普遍量化的”,或简称为“普遍的”。
但是,这不是您的情况:您的函数VehicleReader[T]根据其参数的值返回特定实例,例如CarReader(我认为是 extends VehicleReader[Car])。假设我写了类似的东西:
class House extends Vehicle
val reader = VehicleReader[House](VehicleType.Car)
val house: House = reader.read() // Assuming there is a method VehicleReader[T].read(): T
编译器会很高兴地编译它,但我会ClassCastException在执行这段代码时得到。
对于这种情况,有两种可能的修复方法。首先,您可以使用存在(或存在量化)类型,它可以作为 Java 通配符的更强大版本:
def apply(vehicleType: VehicleType): VehicleReader[_] = ...
这个函数的签名基本上是“你给我一个VehicleType,我给你一个VehicleReader 某种类型的实例”。您将拥有一个类型的对象VehicleReader[_];除了这种类型存在之外,你不能说任何关于它的参数的类型,这就是为什么这种类型被称为存在的。
def apply(vehicleType: VehicleType): VehicleReader[T] forSome {type T} = ...
这是一个等价的定义,从它可能更清楚为什么这些类型具有这样的属性 -T类型隐藏在参数中,所以你对它一无所知,但它确实存在。
但是由于存在的这种属性,您无法真正获得有关真实类型参数的任何信息。比如说,你不能通过直接转换 withVehicleReader[Car]来摆脱它,这很危险,除非你将/用于类型参数并在转换之前检查它。这有时(事实上,大部分时间)是笨拙的。VehicleReader[_]asInstanceOfTypeTagClassTagVehicleReader
这就是第二个选择来救援的地方。您的代码之间有明确的对应关系VehicleType,VehicleReader[T]即当您有特定实例时,VehicleType您肯定知道具体T的VehicleReader[T]签名:
VehicleType.Car -> CarReader (<: VehicleReader[Car])
VehicleType.Truck -> TruckReader (<: VehicleReader[Truck])
等等。
因此,将类型参数添加到VehicleType. 在这种情况下,您的方法将如下所示
def apply[T <: Vehicle](vehicleType: VehicleType[T]): VehicleReader[T] = ...
现在输入类型和输出类型直接相连,这个方法的用户将被迫提供他想要的正确VehicleType[T]实例T。这排除了我之前提到的运行时错误。
你仍然需要asInstanceOf演员。为了避免完全强制转换,您必须将VehicleReader实例化代码(例如您的new CarReader())移动到VehicleType,因为您知道类型参数实际值的唯一地方VehicleType[T]是构造此类型的实例的地方:
sealed trait VehicleType[T <: Vehicle] {
def newReader: VehicleReader[T]
}
object VehicleType {
case object Car extends VehicleType[Car] {
def newReader = new CarReader
}
// ... and so on
}
然后VehicleReader工厂方法看起来非常干净并且是完全类型安全的:
object VehicleReader {
def apply[T <: Vehicle](vehicleType: VehicleType[T]) = vehicleType.newReader
}