我看到了一些我不理解的东西。我有一个(例如)车辆的层次结构,一个对应的VehicalReaders层次结构,以及一个带有apply方法的VehicleReader对象:
1 2 3 4 5 6 7 8 9
| 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]])是出现问题的原因-没有它的结果是编译错误,例如:
1 2 3 4 5
| type mismatch ;
found : CarReader
required : VehicleReader [T ]
case VehicleType. Car => new CarReader ()
^ |
相关问题:
-
为什么编译器不能将CarReader视为VehicleReader [T]?
-
在这种情况下使用的正确的类型参数和返回类型是什么?
我怀疑这里的根本原因是VehicleReader的类型参数是不变的,但使其协变不会改变结果。
我觉得这应该很简单(即在Java中使用通配符很容易实现)。
该问题的起因很简单,实际上与差异无关。考虑甚至更简单的示例:
1 2 3
| object Example {
def gimmeAListOf [T ]: List [T ] = List [Int ](10)
} |
此代码段捕获了代码的主要思想。但这是不正确的:
1
| val list = Example. gimmeAListOf[String ] |
list的类型是什么?我们要求专门针对List[String]的gimmeAListOf方法,但是,它总是返回List[Int](10)。显然,这是一个错误。
因此,换句话说,当方法具有类似method[T]: Example[T]的签名时,它实际上声明:"对于任何类型的T,您给我,我将返回Example[T] \\的实例"。此类类型有时称为"通用量化",或简称为"通用"。
但是,情况并非如此:您的函数根据其参数的值(例如,VehicleReader[T])返回特定的VehicleReader[T]实例。 CarReader(我认为是VehicleReader[Car]的扩展)。假设我写了类似的东西:
1 2 3 4
| 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通配符版本使用:
1
| def apply (vehicleType : VehicleType ): VehicleReader [_] = ... |
此功能的签名基本上读为""您给我一个VehicleType,并且我为您返回了VehicleReader的某种类型的实例"。您将有一个VehicleReader[_]类型的对象;您不能说任何有关其参数类型的信息,除非该类型存在,这就是为什么这种类型称为存在的原因。
1
| def apply (vehicleType : VehicleType ): VehicleReader [T ] forSome {type T } = ... |
这是一个等效的定义,从中可能更清楚为什么这些类型具有此类属性-T类型隐藏在参数内部,因此您对此一无所知,但是它确实存在。 >
但是由于存在属性的这一特性,您实际上无法获得有关实型参数的任何信息。除非通过直接使用asInstanceOf强制转换,否则无法从VehicleReader[_]中获得VehicleReader[Car],这很危险,除非您将TypeTag / ClassTag作为类型参数存储在VehicleReader中,然后在投。有时(实际上,大多数情况下)很笨拙。
这是第二种方法。代码中的VehicleType和VehicleReader[T]之间存在明显的对应关系,即当您具有VehicleType的特定实例时,您肯定知道VehicleReader[T]签名中的具体T:
1 2
| VehicleType.Car -> CarReader (<: VehicleReader[Car])
VehicleType.Truck -> TruckReader (<: VehicleReader[Truck]) |
以此类推。
因此,将类型参数添加到VehicleType是有意义的。在这种情况下,您的方法将类似于
1
| def apply [T <: Vehicle ](vehicleType : VehicleType [T ]): VehicleReader [T ] = ... |
现在直接连接输入类型和输出类型,并且该方法的用户将被迫为其想要的T提供正确的VehicleType[T]实例。这排除了我前面提到的运行时错误。
尽管如此,您仍然需要asInstanceOf强制转换。为了避免完全强制转换,您必须将VehicleReader实例化代码(例如,您的new CarReader())移动到VehicleType,因为唯一知道VehicleType[T]类型参数的实际值的地方就是构造这种类型的实例的位置:
然后VehicleReader工厂方法将看起来非常干净并且是完全类型安全的:
1 2 3
| object VehicleReader {
def apply [T <: Vehicle ](vehicleType : VehicleType [T ]) = vehicleType. newReader
} |
- 您还尝试过在返回类型(即存在性)中使用占位符,但随后导致需要转换返回值。我将探讨您的第二个建议-确实使我感到正确-非常感谢。
-
@ScalaNewb,我已经详细解释了第二种方法。