我看到了一些我不理解的东西。我有(例如)车辆的层次结构,VehicalReaders的相应层次结构,以及带应用方法的VehicleReader对象:
abstract class VehicleReader[T <: Vehicle] {
...
object VehicleReader {
def apply[T <: Vehicle](vehicleId: Int): VehicleReader[T] = apply(vehicleType(vehicleId))
def apply[T <: Vehicle](vehicleType VehicleType): VehicleReader[T] = vehicleType match {
case VehicleType.Car => new CarReader().asInstanceOf[VehicleReader[T]]
...
请注意,如果您有多个apply方法,则必须指定返回类型。当没有必要指定返回类型时,我没有问题。
强制转换(.asInstanceOf [VehicleReader [T]])是问题的原因 - 没有它,结果就是编译错误,如:
type mismatch;
found : CarReader
required: VehicleReader[T]
case VehicleType.Car => new CarReader()
^
相关问题:
我怀疑这里的根本原因是VehicleReader对其类型参数是不变的,但是使其协变不会改变结果。
我觉得这应该是相当简单的(即,使用通配符在Java中很容易实现)。
答案 0 :(得分:5)
问题有一个非常简单的原因,实际上与方差没有任何关系。考虑更简单的例子:
object Example {
def gimmeAListOf[T]: List[T] = List[Int](10)
}
此代码段捕获了代码的主要概念。但这是不正确的:
val list = Example.gimmeAListOf[String]
list
的类型是什么?我们专门针对gimmeAListOf
询问了List[String]
方法,但它总是返回List[Int](10)
。显然,这是一个错误。
所以,换句话说,当方法有method[T]: Example[T]
这样的签名时,它确实声明:“对于任何类型T
你给我我将返回一个Example[T]
的实例。这种类型有时被称为“普遍量化”,或简称为“普遍”。
但是,这不是您的情况:您的函数会返回VehicleReader[T]
的特定实例,具体取决于其参数的值,例如CarReader
(我认为,扩展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
类型隐藏在参数内部,所以你不知道它有什么,但确实存在。< / p>
但是由于存在性的这种属性,你无法真正获得有关真实类型参数的任何信息。除非您使用VehicleReader[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
}