Scala:键入参数和继承

时间:2013-09-06 14:46:01

标签: scala

我看到了一些我不理解的东西。我有(例如)车辆的层次结构,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()
                                  ^

相关问题:

  • 为什么编译器不能将CarReader看作VehicleReader [T]?
  • 在这种情况下使用的正确类型参数和返回类型是什么?

我怀疑这里的根本原因是VehicleReader对其类型参数是不变的,但是使其协变不会改变结果。

我觉得这应该是相当简单的(即,使用通配符在Java中很容易实现)。

1 个答案:

答案 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中的参数并在演员表前检查。这有时(事实上,大部分时间)都很笨拙。

这就是第二个选择来救援的地方。您的代码中VehicleTypeVehicleReader[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
}