[英]Scala: Type parameters and inheritance
我看到了我不明白的东西。 我有一个(例如)车辆的层次结构,一个对应的VehicalReaders层次结构,以及一个带有apply方法的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]]
...
请注意,当您有多个应用方法时,必须指定返回类型。 无需指定返回类型时,我没有任何问题。
强制转换(.asInstanceOf [VehicleReader [T]])是出现问题的原因-没有它的结果是编译错误,例如:
type mismatch;
found : CarReader
required: VehicleReader[T]
case VehicleType.Car => new CarReader()
^
相关问题:
我怀疑这里的根本原因是VehicleReader的类型参数是不变的,但使其协变不会改变结果。
我觉得这应该很简单(即,在Java中使用通配符很容易实现)。
该问题的起因很简单,实际上与差异无关。 考虑甚至更简单的示例:
object Example {
def gimmeAListOf[T]: List[T] = List[Int](10)
}
此代码段捕获了代码的主要思想。 但这是不正确的:
val list = Example.gimmeAListOf[String]
list
的类型是什么? 我们要求专门用于List[String]
gimmeAListOf
方法,但是,它总是返回List[Int](10)
。 显然,这是一个错误。
因此,换句话说,当该方法具有诸如method[T]: Example[T]
的签名时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
类型隐藏在参数内部,因此您对此一无所知,但是它确实存在。
但是由于存在性的这种特性,您实际上无法获得有关实型参数的任何信息。 例如,除非通过使用asInstanceOf
进行直接asInstanceOf
,否则无法从VehicleReader[_]
获取VehicleReader[Car]
,这很危险,除非您将TypeTag
/ ClassTag
存储在VehicleReader
并在VehicleReader
之前VehicleReader
进行检查。 有时(实际上,大多数时候)这很笨拙。
这就是第二种选择。 代码中VehicleType
和VehicleReader[T]
之间有明显的对应关系,即,当您拥有VehicleType
特定实例时,您肯定知道VehicleReader[T]
签名中的具体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
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.