0

我看到了一些我不明白的东西。我有一个(比如说)车辆的层次结构,一个相应的 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 中使用通配符完成)。

4

1 回答 1

5

这个问题有一个非常简单的原因,实际上与方差没有任何关系。考虑更简单的例子:

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

这就是第二个选择来救援的地方。您的代码之间有明确的对应关系VehicleTypeVehicleReader[T]即当您有特定实例时,VehicleType您肯定知道具体TVehicleReader[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
}
于 2013-09-06T16:07:35.310 回答