小编典典

如何绕过 Scala 上的类型擦除?或者,为什么我不能获取我的集合的类型参数?

all

在 Scala 上,一个可悲的事实是,如果您实例化一个 List[Int],您可以验证您的实例是一个 List,并且您可以验证它的任何单个元素是一个
Int,但不能验证它是一个 List[ Int],可以很容易地验证:

scala> List(1,2,3) match {
     | case l : List[String] => println("A list of strings?!")
     | case _ => println("Ok")
     | }
warning: there were unchecked warnings; re-run with -unchecked for details
A list of strings?!

-unchecked 选项将责任归咎于类型擦除:

scala>  List(1,2,3) match {
     |  case l : List[String] => println("A list of strings?!")
     |  case _ => println("Ok")
     |  }
<console>:6: warning: non variable type-argument String in type pattern is unchecked since it is eliminated by erasure
        case l : List[String] => println("A list of strings?!")
                 ^
A list of strings?!

为什么会这样,我该如何解决?


阅读 107

收藏
2022-03-24

共1个答案

小编典典

此答案使用Manifest-API,自 Scala 2.10 起已弃用。请参阅下面的答案以获取更多当前解决方案。

Scala 是使用类型擦除定义的,因为 Java 虚拟机 (JVM) 与 Java
不同,没有获得泛型。这意味着,在运行时,只存在类,而不存在其类型参数。在示例中,JVM
知道它正在处理一个scala.collection.immutable.List,但不知道这个列表是用 参数化的Int

幸运的是,Scala 中有一个特性可以让你绕过这个问题。它是 清单 。Manifest
是类,其实例是表示类型的对象。由于这些实例是对象,您可以传递它们、存储它们,并且通常在它们上调用方法。在隐式参数的支持下,它成为一个非常强大的工具。举个例子,例如:

object Registry {
  import scala.reflect.Manifest

  private var map= Map.empty[Any,(Manifest[_], Any)]

  def register[T](name: Any, item: T)(implicit m: Manifest[T]) {
    map = map.updated(name, m -> item)
  }

  def get[T](key:Any)(implicit m : Manifest[T]): Option[T] = {
    map get key flatMap {
      case (om, s) => if (om <:< m) Some(s.asInstanceOf[T]) else None
    }     
  }
}

scala> Registry.register("a", List(1,2,3))

scala> Registry.get[List[Int]]("a")
res6: Option[List[Int]] = Some(List(1, 2, 3))

scala> Registry.get[List[String]]("a")
res7: Option[List[String]] = None

存储元素时,我们也存储它的“清单”。Manifest 是一个类,其实例代表 Scala 类型。这些对象比 JVM
拥有更多的信息,这使我们能够测试完整的参数化类型。

但是请注意,aManifest仍然是一个不断发展的特征。作为其局限性的一个例子,它目前对方差一无所知,并假设一切都是协变的。我希望一旦目前正在开发的
Scala 反射库完成,它将变得更加稳定和可靠。

2022-03-24