在 Scala 中对泛型类型使用 asInstanceOf

Using asInstanceOf on Generic Types in Scala

说,我有一个 class 这样的:

class Funky[A, B](val foo: A, val bar: B) {
  override def toString: String = s"Funky($foo, $bar)"
}

使用如下所示的一些方法:

def cast(t: Any): Option[Funky[A, B]] = {
  if (t == null) None
  else if (t.isInstanceOf[Funky[_, _]]) {
    val o = t.asInstanceOf[Funky[_, _]]
    for {
      _ <- typA.cast(o.foo)
      _ <- typB.cast(o.bar)
    } yield o.asInstanceOf[Funky[A, B]]
  } else None
}

isInstanceOf 和 asInstanceOf 是如何工作的?运行时没有关于 Funky 中包含的实际类型的信息。那么这段代码是如何工作的呢?有什么线索吗?

您可以检查元素是否为 特定 类型,例如:

Funky(1, 2).foo.isInstanceOf[String] // false
Funky(1, 2).foo.isInstanceOf[Int] // true

但是如果您尝试检查 generic 类型,它将不起作用。例如:

def check[A](x: Any) = x.isInstanceOf[A]
check[String](1) // true
check[String](Funky(1, 2).foo) // true

并且编译器会给出一条解释错误的警告消息:

abstract type A is unchecked since it is eliminated by erasure

但是,您显示的代码似乎可以通过此处的其他方法解决此问题:

_ <- typA.cast(o.foo)
_ <- typB.cast(o.bar)

没有看到这些对象的实现,我猜他们有某种 TypeTagClassTag 并使用它。这几乎总是解决擦除问题的推荐方法。

我们拆开看吧

假设您以某种方式获得实例 typA: Typable[A]typB: Typable[B],因此 typA 有一个方法

def cast(a: Any): Option[A] = ...

typB 也是如此。如果参数确实是 A 类型,则 cast 方法应为 return Some[A],否则为 None。这些实例显然可以为原始类型 IntString 构造(它们已经由库提供)。

现在您想使用 typAtypBFunky[A, B] 实现 cast

null检查应该很清楚,你可以对任何东西执行它。但接下来是第一个 isInstanceOf:

  else if (t.isInstanceOf[Funky[_, _]]) {

请注意,Funky 的类型参数已被下划线替换。这是因为 Funky 的通用参数 被擦除,并且在 运行 时不可用。然而,我们仍然可以区分 Funky[_, _]Map[_, _],因为参数化类型本身被保留,即使参数被擦除。此外,isInstanceOf 甚至可以区分 TreeMapHashMap,即使两个实例都有编译时类型 Map:运行time 类型可用,只是忘记了通用参数。

同样,一旦知道 tFunky 类型,就可以将其转换为 Funky[_, _]

    val o = t.asInstanceOf[Funky[_, _]]

用现有类型替换泛型参数。这意味着:你只知道有 some 类型 XY 这样 oFunky[X, Y] 类型,但是你不知道 XY 是什么。但是,现在您至少知道 o 有方法 foobar(即使您不知道它们的 return 类型是什么)。

但现在您可以将 o.fooo.bar 放入 typA.casttypeB.cast 中。 monadic 绑定左侧的下划线表示您丢弃 AB 类型的实例,它们被 return 包裹在 Some 中。你只关心两个演员都不 return a None:

    for {
      _ <- typA.cast(o.foo)
      _ <- typB.cast(o.bar)
    } yield /* ... */

如果其中一个转换失败并且 return 编辑了一个 None,整个单子表达式的计算结果将是 None,因此该方法将 return None,表示整体转换为Funky[A, B]失败。 如果两个转换都成功,那么我们知道 o 确实是 Funky[A, B] 类型,所以我们可以将 o 转换为 Funky[A, B]:

o.asInstanceOf[Funky[A, B]]

你可能会想 "how is this possible, we don't know anything about A and B at runtime!",但这没关系,因为这个 asInstanceOf 只是为了满足编译器的类型检查阶段。它不能在运行时间做任何事情,因为它只能检查Funky部分,而不能检查擦除的参数AB ].

以下是该现象的简短说明:

val m: Map[Long, Double] = Map(2L -> 100d)
val what = m.asInstanceOf[Map[Int, String]]
println("It compiles, and the program does not throw any exceptions!")

只需将其保存为脚本并将其提供给 scala 解释器。它会毫无怨言地编译和 运行,因为 asInstanceOfMap 之外的任何东西都是盲目的。所以,第二个 asInstanceOf 只是为了说服类型检查器 returned 值确实是 Funky[A, B] 类型,程序员有责任不做任何荒谬的说法。

总结一下:isInstanceOf 是在 运行 时间做某事的东西(它检查实例是否符合某种具体类型,并且 returns 运行 时间-值 true-false)。 asInstanceOf 有两个不同的功能。第一个(转换为具体的 class Funky[_, _])在 运行 时有副作用(转换可能会失败并抛出异常)。第二个(asInstanceOf[Funky[A, B]])只是为了满足编译时的类型检查阶段。

希望这能有所帮助。