如何正确地使用 isInstanceOf[] 方法进行包装?

3
我想要构建一个对 isInstanceOf[T]asInstanceOf[T] 进行包装的容器,它会输出 Option[T] 并提供方便的 mapgetOrElse 方法。
所以我试了一下,但结果让我失望了。
import scala.reflect.runtime.universe.{TypeTag, typeOf}

class Base()
class Deep() extends Base
class Deeper() extends Deep()

final case class WrapSimple[T](source : T) {
  def cast[U] : Option[U] =
    if (source.isInstanceOf[U]) Some(source.asInstanceOf[U]) else None
}

final case class WrapFullTagged[T: TypeTag](source : T) {
  def cast[U : TypeTag] : Option[U] =
    if (typeOf[T] <:< typeOf[U]) Some(source.asInstanceOf[U]) else None
}

final case class WrapHalfTagged[T](source : T) {
  val stpe = {
    val clazz = source.getClass
    val mirror = scala.reflect.runtime.universe.runtimeMirror(clazz.getClassLoader)
    mirror.classSymbol(clazz).toType
  }
  def cast[U : TypeTag] : Option[U] =
    if (stpe <:< typeOf[U]) Some(source.asInstanceOf[U]) else None
}

object Test {
  val base = new Base
  val deep = new Deep
  val deeper = new Deeper
  val wile : Deep = new Deeper

  def testSimple() : Unit = {
    println(WrapSimple(deep).cast[Base].isDefined) // should be true
    println(WrapSimple(deep).cast[Deeper].isDefined) // should be false
    println(WrapSimple(wile).cast[Deeper].isDefined) // should be true
  }

  def testFullTagged() : Unit = {
    println(WrapFullTagged(deep).cast[Base].isDefined) // should be true
    println(WrapFullTagged(deep).cast[Deeper].isDefined) // should be false
    println(WrapFullTagged(wile).cast[Deeper].isDefined) // should be true
  }

  def testHalfTagged() : Unit = {
    println(WrapHalfTagged(deep).cast[Base].isDefined) // should be true
    println(WrapHalfTagged(deep).cast[Deeper].isDefined) // should be false
    println(WrapHalfTagged(wile).cast[Deeper].isDefined) // should be true
  }

  def testAll() : Unit = {
    testSimple()
    testFullTagged()
    testHalfTagged()
  }
}
WrapSimple 看起来不错,但它无法正常工作,它会擦除 isInstanceOf[U] 方法中的 U 类型,因此它总是返回 true。有趣的是,asInstanceOf[U] 通常会保留 U 类型,所以它只会产生运行时异常。

我尝试过的第二种方法是使用类型标签的 WrapFullTagged。它似乎很清晰,但同样违反了契约。它只能在编译时检查静态类型,并且对运行时实际类型没有任何了解。

因此,我结合了两种方法,产生了第三种方法,至少能够产生正确的输出。但它看起来很糟糕,并调用了具有巨大代价的反射功能。

是否可能以更高雅的方式解决这个问题?


1
你永远无法在运行时获得有关类型的深入洞察,因为它们被擦除了。如果您通过TypeTag在编译时保留它,则可以在运行时拥有所有类型信息的唯一方法。在第一个示例中,TU都被擦除了。您能否命名一个使用案例,其中WrapFullTagged不能提供所需的结果? - Michael Zajac
每当您想使用isInstanceOf时,这通常发生在您不知道运行时变量背后的实际类型时。这种情况经常发生。您可以尝试回忆一下上次编写没有任何模式匹配的项目是什么时候。 - ayvango
1
shapeless typeable https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/typeable.scala - som-snytt
宏替换可能更容易实现,但这可能是一个不错的选择。 - ayvango
1个回答

2

请查看 scala.reflect.ClassTag。它提供了访问擦除后的类类型的方法,并且根据API文档,可以用于以下函数类型。

def unapply(x: Any): Option[T]

ClassTag[T]可以作为一个提取器,仅匹配类型为T的对象。

一个符合问题预期输出并看起来比较优雅的例子:

class Base()
class Deep() extends Base
class Deeper() extends Deep()

case object SimpleCaster {
  def cast[A](t: Any)(implicit classTag: scala.reflect.ClassTag[A]): Option[A] = classTag.unapply(t)
}

object Test {
  val base = new Base
  val deep = new Deep
  val deeper = new Deeper
  val wile: Deep = new Deeper

  def testSimple(): Unit = {
    val a = SimpleCaster.cast[Base](deep)
    val b = SimpleCaster.cast[Deeper](deep)
    val c = SimpleCaster.cast[Deeper](wile)
    println(s"${a.isDefined} - ${a.map(_.getClass)}")
    println(s"${b.isDefined} - ${b.map(_.getClass)}")
    println(s"${c.isDefined} - ${c.map(_.getClass)}")
  }
}

在控制台输出中显示结果:

scala> Test.testSimple
true - Some(class Deep)
false - None
true - Some(class Deeper)

简而言之,虽然这使用了反射API,但它看起来是一个实用的解决方案,不会过于冗长。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接