如何绕过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 }