在Scala中的通用类型上使用asInstanceOf

时间:2018-01-24 18:23:07

标签: scala shapeless

说,我有一个这样的课:

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如何工作? Runtime没有关于Funky中包含的实际类型的信息。那么这段代码是如何工作的呢?有线索吗?

2 个答案:

答案 0 :(得分:3)

让我们把它分开。

假设您以某种方式获取实例typA: Typable[A]typB: Typable[B],以便typA有方法

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

,同样适用于typB。如果参数确实是cast类型,则Some[A]方法应返回A,否则返回None。显然可以为原始类型IntString构建这些实例(它们已经由库提供)。

现在,您要使用typAtypBcast实施Funky[A, B]

null检查应该清楚,你可以对任何事情进行检查。但接下来是第一个isInstanceOf

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

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

同样,一旦您知道t属于Funky类型,就可以将其投入 Funky[_, _]

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

用存在类型替换泛型参数。这意味着:您只知道某些类型XYo类型为Funky[X, Y],但您不知道知道那些XY是什么。但是,现在您至少知道o有方法foobar(即使您不知道它们的返回类型是什么)。

但现在您可以将o.fooo.bar带到typA.casttypeB.cast。 monadic绑定左侧的下划线表示您丢弃AB类型的实例,这些实例将返回包含在Some中。您只关心两个演员都不会返回None

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

如果其中一个强制转换失败并返回None,则整个monadic表达式将计算为None,因此该方法将返回None,表示整体转换为Funky[A, B] {1}}失败了。 如果两个演员都成功,那么我们知道o确实属于Funky[A, B]类型,因此我们可以将o投射到Funky[A, B]中:

o.asInstanceOf[Funky[A, B]]

您可能想知道“这怎么可能,我们在运行时对AB一无所知!”,但这没关系,因为asInstanceOf只有Funky满足编译器的类型检查阶段。它在运行时无法执行任何操作,因为它只能检查A部分,而不能检查已删除的参数Bval 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

只需将其另存为脚本并将其提供给asInstanceOf解释程序即可。它会在没有投诉的情况下编译和运行,因为MapasInstanceOf以外的任何内容都是盲目的。所以,第二个Funky[A, B]只是为了说服类型检查器返回的值确实是类型isInstanceOf,并且程序员有责任不做任何荒谬的声明。

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

希望这有所帮助。

答案 1 :(得分:0)

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

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

但是如果您尝试检查泛型类型,它将无效。例如:

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

编译器会给出一条警告消息,说明错误:

  

抽象类型A未被选中,因为它被删除删除

但是,您展示的代码似乎是通过其他方法解决这个问题:

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

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