在 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?!
为什么会这样,我该如何解决?
此答案使用Manifest-API,自 Scala 2.10 起已弃用。请参阅下面的答案以获取更多当前解决方案。
Manifest
Scala 是使用类型擦除定义的,因为 Java 虚拟机 (JVM) 与 Java 不同,没有获得泛型。这意味着,在运行时,只存在类,而不存在其类型参数。在示例中,JVM 知道它正在处理一个scala.collection.immutable.List,但不知道这个列表是用 参数化的Int。
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 反射库完成,它将变得更加稳定和可靠。