繁体   English   中英

Scala:类型参数和继承

[英]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()
                                  ^

相关问题:

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

我怀疑这里的根本原因是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进行检查。 有时(实际上,大多数时候)这很笨拙。

这就是第二种选择。 代码中VehicleTypeVehicleReader[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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM