如何绕过Scala上的types擦除? 或者,为什么我不能得到我的集合的types参数?

如果你实例化一个List [Int],你可以validation你的实例是一个List,你可以validation它的任何单独的元素是一个Int,但不是一个List [诠释],可以很容易地validation:

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选项正确地指出types擦除:

 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开始已经弃用了。 请参阅下面的答案以获取更多当前的解

Scala被定义为Type Erasure,因为Java虚拟机(Java Virtual Machine,JVM)与Java不同,没有得到generics。 这意味着,在运行时,只有类存在,而不是它的types参数。 在这个例子中,JVM知道它正在处理一个scala.collection.immutable.List ,但不是这个列表是用Int来参数化的。

幸运的是,Scala中有一个function可以让你解决这个问题。 这是清单 。 Manifest是其实例是表示types的对象的类。 由于这些实例是对象,因此可以将它们传递,存储,并通常调用方法。 在隐式参数的支持下,它成为一个非常强大的工具。 以下面的例子为例:

 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”。 Manifest是一个实例代表Scalatypes的类。 这些对象具有比JVM更多的信息,这使我们能够testing完整的参数化types。

但是请注意, Manifest仍然是一个不断发展的function。 作为其局限性的一个例子,它目前不知道任何差异,并假设一切都是共变的。 我希望一旦目前正在开发的Scalareflection库完成,它将会变得更加稳定和稳定。

你可以使用TypeTags来做到这一点(就像Daniel已经提到的那样,但是我只是明确地把它拼出来):

 import scala.reflect.runtime.universe._ def matchList[A: TypeTag](list: List[A]) = list match { case strlist: List[String @unchecked] if typeOf[A] =:= typeOf[String] => println("A list of strings!") case intlist: List[Int @unchecked] if typeOf[A] =:= typeOf[Int] => println("A list of ints!") } 

你也可以使用ClassTags来做到这一点(这样可以避免依赖scala-reflect):

 import scala.reflect.{ClassTag, classTag} def matchList2[A : ClassTag](list: List[A]) = list match { case strlist: List[String @unchecked] if classTag[A] == classTag[String] => println("A List of strings!") case intlist: List[Int @unchecked] if classTag[A] == classTag[Int] => println("A list of ints!") } 

只要您不希望types参数A本身是genericstypes,就可以使用ClassTag。

不幸的是它有点冗长,你需要@unchecked注释来抑制编译器警告。 TypeTag可能会被编译器自动整合到模式匹配中: https ://issues.scala-lang.org/browse/SI-6517

你可以使用无forms的Typeable类来获得你的结果,

示例REPL会话,

 scala> import shapeless.syntax.typeable._ import shapeless.syntax.typeable._ scala> val l1 : Any = List(1,2,3) l1: Any = List(1, 2, 3) scala> l1.cast[List[String]] res0: Option[List[String]] = None scala> l1.cast[List[Int]] res1: Option[List[Int]] = Some(List(1, 2, 3)) 

在给定范围的Typeable实例可用的情况下, cast操作将尽可能精确地擦除。

我想出了一个相对简单的解决scheme,只要在有限的使用情况下就足够了,本质上就是包装参数化的types,这些参数化的types可能会受到匹配语句中使用的包装类中的types擦除问题的困扰。

 case class StringListHolder(list:List[String]) StringListHolder(List("str1","str2")) match { case holder: StringListHolder => holder.list foreach println } 

这具有预期的输出,并将我们的case类的内容限制为所需的typesString列表。

更多细节在这里: http : //www.scalafied.com/?p=60

有一种方法可以克服Scala中的types擦除问题。 在匹配1中 克服types擦除和匹配2中的克隆types擦除(方差)是如何编写一些帮助程序来包装types(包括方差)进行匹配的一些说明。

我发现了一个稍微好一点的解决方法,用于限制这种非常棒的语言。

在Scala中,types擦除的问题不会发生在数组中。 我认为用一个例子来certificate这一点比较容易。

假设我们有一个(Int, String)的列表,那么下面给出一个types擦除警告

 x match { case l:List[(Int, String)] => ... } 

要解决这个问题,首先创build一个案例类:

 case class IntString(i:Int, s:String) 

然后在模式匹配中做类似的事情:

 x match { case a:Array[IntString] => ... } 

这似乎是完美的工作。

这将需要在你的代码中进行一些小的修改来处理数组而不是列表,但是不应该是一个主要的问题。

请注意,使用case a:Array[(Int, String)]仍然会提供一个types擦除警告,所以有必要使用新的容器类(在本例中为IntString )。

我想知道这是否是一个适合的解决方法:

 scala> List(1,2,3) match { | case List(_: String, _*) => println("A list of strings?!") | case _ => println("Ok") | } 

它不符合“空列表”的情况,但它给出了一个编译错误,不是一个警告!

 error: type mismatch; found: String requirerd: Int 

另一方面,这似乎工作….

 scala> List(1,2,3) match { | case List(_: Int, _*) => println("A list of ints") | case _ => println("Ok") | } 

这不是更好,还是我错过了这一点?

由于Java不知道实际的元素types,所以我发现使用List[_]最有用。 然后,警告消失,代码描述了现实 – 这是一个未知的列表。

不是一个解决scheme,而是一种生活方式,而不是彻底清除它:添加@unchecked注释。 看到这里 – http://www.scala-lang.org/api/current/index.html#scala.unchecked

使用模式匹配守卫

  list match { case x:List if x.isInstanceOf(List[String]) => do sth case x:List if x.isInstanceOf(List[Int]) => do sth else }