如何解决 Scala 上的类型擦除问题?或者,为什么我不能获得我的集合的类型参数?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1094173/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I get around type erasure on Scala? Or, why can't I get the type parameter of my collections?
提问by Daniel C. Sobral
It's a sad fact of life on Scala that if you instantiate a List[Int], you can verify that your instance is a List, and you can verify that any individual element of it is an Int, but not that it is a List[Int], as can be easily verified:
在 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?!
The -unchecked option puts the blame squarely on type erasure:
-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?!
Why is that, and how do I get around it?
为什么会这样,我该如何解决?
采纳答案by Daniel C. Sobral
This answer uses the
Manifest-API, which is deprecated as of Scala 2.10. Please see answers below for more current solutions.
此答案使用
Manifest-API,自 Scala 2.10 起已弃用。有关更多当前解决方案,请参阅下面的答案。
Scala was defined with Type Erasure because the Java Virtual Machine (JVM), unlike Java, did not get generics. This means that, at run time, only the class exists, not its type parameters. In the example, JVM knows it is handling a scala.collection.immutable.List, but not that this list is parameterized with Int.
Scala 被定义为类型擦除,因为与 Java 不同,Java 虚拟机 (JVM) 没有泛型。这意味着,在运行时,只存在类,而不存在其类型参数。在这个例子中,JVM 知道它正在处理scala.collection.immutable.List,但不知道这个列表是用 参数化的Int。
Fortunately, there's a feature in Scala that lets you get around that. It's the Manifest. A Manifest is class whose instances are objects representing types. Since these instances are objects, you can pass them around, store them, and generally call methods on them. With the support of implicit parameters, it becomes a very powerful tool. Take the following example, for instance:
幸运的是,Scala 中有一项功能可以让您解决这个问题。这是清单。清单是其实例是表示类型的对象的类。由于这些实例是对象,您可以传递它们、存储它们,并且通常对它们调用方法。在隐式参数的支持下,它成为了一个非常强大的工具。以下面的例子为例:
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
When storing an element, we store a "Manifest" of it too. A Manifest is a class whose instances represent Scala types. These objects have more information than JVM does, which enable us to test for the full, parameterized type.
存储元素时,我们也存储它的“清单”。清单是一个类,其实例代表 Scala 类型。这些对象比 JVM 拥有更多的信息,这使我们能够测试完整的参数化类型。
Note, however, that a Manifestis still an evolving feature. As an example of its limitations, it presently doesn't know anything about variance, and assumes everything is co-variant. I expect it will get more stable and solid once the Scala reflection library, presently under development, gets finished.
但是请注意, aManifest仍然是一个不断发展的功能。作为其局限性的一个例子,它目前对方差一无所知,并假设一切都是协变的。我希望在目前正在开发的 Scala 反射库完成后,它会变得更加稳定和可靠。
回答by tksfz
You can do this using TypeTags (as Daniel already mentions, but I'll just spell it out explicitly):
您可以使用 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!")
}
You can also do this using ClassTags (which saves you from having to depend on scala-reflect):
你也可以使用 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!")
}
ClassTags can be used so long as you don't expect the type parameter Ato itself be a generic type.
只要您不希望类型参数A本身是泛型类型,就可以使用 ClassTags 。
Unfortunately it's a little verbose and you need the @unchecked annotation to suppress a compiler warning. The TypeTag may be incorporated into the pattern match automatically by the compiler in the future: https://issues.scala-lang.org/browse/SI-6517
不幸的是,它有点冗长,您需要@unchecked 注释来抑制编译器警告。TypeTag 将来可能会被编译器自动合并到模式匹配中:https://issues.scala-lang.org/browse/SI-6517
回答by Miles Sabin
You can use the Typeabletype class from shapelessto get the result you're after,
您可以使用shapeless 中的Typeable类型类来获得您想要的结果,
Sample REPL session,
示例 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))
The castoperation will be as precise wrt erasure as possible given the in-scope Typeableinstances available.
cast考虑到Typeable可用的范围内实例,操作将尽可能精确地擦除。
回答by thricejamie
I came up with a relatively simple solution that would suffice in limited-use situations, essentially wrapping parameterized types that would suffer from the type erasure problem in wrapper classes that can be used in a match statement.
我想出了一个相对简单的解决方案,它在有限使用的情况下就足够了,本质上是包装参数化类型,这些类型会在可用于匹配语句的包装类中受到类型擦除问题的影响。
case class StringListHolder(list:List[String])
StringListHolder(List("str1","str2")) match {
case holder: StringListHolder => holder.list foreach println
}
This has the expected output and limits the contents of our case class to the desired type, String Lists.
这具有预期的输出,并将我们的案例类的内容限制为所需的类型,字符串列表。
More details here: http://www.scalafied.com/?p=60
更多细节在这里:http: //www.scalafied.com/?p=60
回答by Alex
There is a way to overcome the type erasure issue in Scala. In Overcoming Type Erasure in matching 1and Overcoming Type Erasure in Matching 2 (Variance)are some explanation of how to code some helpers to wrap the types, including Variance, for matching.
有一种方法可以克服 Scala 中的类型擦除问题。在克服类型擦除在匹配1和克服类型擦除在匹配2(方差)是如何代码一些助手来包装类型,包括方差,用于匹配一些解释。
回答by Jus12
I found a slightly better workaround for this limitation of the otherwise awesome language.
对于这个很棒的语言的限制,我找到了一个稍微好一点的解决方法。
In Scala, the issue of type erasure does not occur with arrays. I think it is easier to demonstrate this with an example.
在 Scala 中,数组不会发生类型擦除的问题。我认为用一个例子来证明这一点更容易。
Let us say we have a list of (Int, String), then the following gives a type erasure warning
假设我们有一个列表(Int, String),那么下面给出了一个类型擦除警告
x match {
case l:List[(Int, String)] =>
...
}
To work around this, first create a case class:
要解决此问题,请首先创建一个案例类:
case class IntString(i:Int, s:String)
then in the pattern matching do something like:
然后在模式匹配中执行以下操作:
x match {
case a:Array[IntString] =>
...
}
which seems to work perfectly.
这似乎完美地工作。
This will require minor changes in your code to work with arrays instead of lists, but should not be a major problem.
这将需要对您的代码进行微小的更改才能使用数组而不是列表,但这应该不是主要问题。
Note that using case a:Array[(Int, String)]will still give a type erasure warning, so it is necessary to use a new container class (in this example, IntString).
请注意, usingcase a:Array[(Int, String)]仍然会给出类型擦除警告,因此需要使用新的容器类(在本例中为IntString)。
回答by rained_in
Since Java does not know the actual element type, I found it most useful to just use List[_]. Then the warning goes away and the code describes reality - it is a list of something unknown.
由于 Java 不知道实际的元素类型,我发现只使用List[_]. 然后警告消失,代码描述现实——它是一个未知事物的列表。
回答by agilesteel
I'm wondering if this is a suited workaround:
我想知道这是否是合适的解决方法:
scala> List(1,2,3) match {
| case List(_: String, _*) => println("A list of strings?!")
| case _ => println("Ok")
| }
It does not match the "empty list" case, but it gives a compile error, not a warning!
它与“空列表”情况不匹配,但它给出了编译错误,而不是警告!
error: type mismatch;
found: String
requirerd: Int
This on the other hand seems to work....
另一方面,这似乎有效......
scala> List(1,2,3) match {
| case List(_: Int, _*) => println("A list of ints")
| case _ => println("Ok")
| }
Isn't it kinda even better or am I missing the point here?
这不是更好,还是我在这里错过了重点?
回答by matanster
Not a solution but a way to live with it without sweeping it under the rug altogether:
Adding the @uncheckedannotation. See here - http://www.scala-lang.org/api/current/index.html#scala.unchecked
不是一个解决方案,而是一种在不完全掩盖它的情况下接受它的方法:添加@unchecked注释。见这里 - http://www.scala-lang.org/api/current/index.html#scala.unchecked
回答by Steve Robinson-Burns
I wanted to add an answer which generalises the problem to: How do a get a String representation of the type of my list at runtime
我想添加一个将问题概括为的答案:如何在运行时获取我的列表类型的字符串表示
import scala.reflect.runtime.universe._
def whatListAmI[A : TypeTag](list : List[A]) = {
if (typeTag[A] == typeTag[java.lang.String]) // note that typeTag[String] does not match due to type alias being a different type
println("its a String")
else if (typeTag[A] == typeTag[Int])
println("its a Int")
s"A List of ${typeTag[A].tpe.toString}"
}
val listInt = List(1,2,3)
val listString = List("a", "b", "c")
println(whatListAmI(listInt))
println(whatListAmI(listString))

