这个问题有一个非常简单的原因,实际上与方差没有任何关系。考虑更简单的例子:
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[_]
asInstanceOf
TypeTag
ClassTag
VehicleReader
这就是第二个选择来救援的地方。您的代码之间有明确的对应关系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
}