如何在Scala上避免类型擦除?或者,为什么可以';我不能获取集合的类型参数吗?

如何在Scala上避免类型擦除?或者,为什么可以';我不能获取集合的类型参数吗?,scala,type-erasure,Scala,Type Erasure,Scala上的一个悲惨事实是,如果实例化一个列表[Int],您可以验证您的实例是一个列表,您可以验证它的任何单个元素是一个Int,但不能验证它是一个列表[Int],这很容易验证: scala> List(1,2,3) match { | case l : List[String] => println("A list of strings?!") | case _ => println("Ok") | } warning: there were u

Scala上的一个悲惨事实是,如果实例化一个列表[Int],您可以验证您的实例是一个列表,您可以验证它的任何单个元素是一个Int,但不能验证它是一个列表[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?!
scala>列表(1,2,3)匹配{
|案例1:List[String]=>println(“字符串列表?!”)
|案例=>println(“确定”)
|  }
:6:警告:类型模式中的非变量类型参数字符串未选中,因为它已通过擦除消除
案例1:List[String]=>println(“字符串列表?!”)
^
一个字符串列表?!
为什么会这样,我该如何应对呢

这个答案使用的是
清单
-API,它在Scala 2.10中被弃用。更多当前解决方案,请参见下面的答案。 Scala是用类型擦除定义的,因为Java虚拟机(JVM)与Java不同,没有得到泛型。这意味着,在运行时,只有类存在,而不存在其类型参数。在本例中,JVM知道它正在处理一个
scala.collection.immutable.List
,但并不知道这个列表是用
Int
参数化的

幸运的是,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
对象注册表{
导入scala.reflect.Manifest
private-var-map=map.empty[Any,(Manifest[\u],Any)]
def寄存器[T](名称:任意,项:T)(隐式m:Manifest[T]){
map=map.updated(名称,m->item)
}
def get[T](键:任意)(隐式m:Manifest[T]):选项[T]={
地图获取关键平面图{
案例(om,s)=>if(om注册表寄存器(“a”,列表(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
在存储元素时,我们也存储元素的“清单”。清单是一个类,其实例表示Scala类型。这些对象比JVM具有更多的信息,这使我们能够测试完整的参数化类型


然而,请注意,
清单仍然是一个不断发展的特性。作为其局限性的一个例子,它目前不知道任何变化,并且假设所有东西都是协变的。我希望在目前正在开发的Scala反射库完成后,它会变得更加稳定和可靠。

我发现有一个稍微不同的特性对于其他令人敬畏的语言的这一限制,tter解决方案

在Scala中,数组不会出现类型擦除的问题。我认为用一个例子来说明这一点比较容易

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

x match {
  case l:List[(Int, String)] => 
  ...
}
要解决此问题,请首先创建一个案例类:

case class IntString(i:Int, s:String)
然后在模式匹配中执行以下操作:

x match {
  case a:Array[IntString] => 
  ...
}
这似乎很有效

这将需要在代码中做一些小的更改来处理数组而不是列表,但这不应该是一个大问题


请注意,使用
案例a:Array[(Int,String)]
仍将给出类型擦除警告,因此有必要使用新的容器类(在本例中,
IntString
).

有一种方法可以克服Scala中的类型擦除问题。在和中,有一些解释说明了如何编写一些帮助程序来包装类型,包括差异,以进行匹配。

我提出了一个相对简单的解决方案,可以满足有限的使用情况,基本上是包装会受到类型影响的参数化类型可在match语句中使用的包装类中存在擦除问题

case class StringListHolder(list:List[String])

StringListHolder(List("str1","str2")) match {
    case holder: StringListHolder => holder.list foreach println
}
这具有预期的输出,并将case类的内容限制为所需的类型String list


这里有更多详细信息:

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

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")
     | }

这不是更好吗,还是我没有抓住要点?

您可以使用中的
Typeable
type类来获得您想要的结果

示例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))

cast
操作将尽可能精确地进行wrt擦除,因为有范围内的
Typeable
实例可用。

由于Java不知道实际的元素类型,我发现只使用
List[\ux]最有用
。然后警告消失了,代码描述了现实情况-这是一个未知事物的列表。

您可以使用类型标签来完成此操作(正如Daniel已经提到的,但我将明确地解释):

您还可以使用ClassTag来实现这一点(这样您就不必依赖scala reflect):

只要类型参数
A
本身不是泛型类型,就可以使用类标记


不幸的是,它有点冗长,您需要@unchecked注释来抑制编译器警告。TypeTag可能会在将来被编译器自动合并到模式匹配中:

不是一种解决方案,而是一种不必完全掩盖它的方式:
添加未选中的
@注释。请参见此处-

使用模式匹配保护

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

我想添加一个答案,将问题概括为:如何在运行时获取列表类型的字符串表示

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))

get
方法可以定义为
for((om,v)@Aaron这是一个很好的建议,但我担心它可能会模糊Scala新手的代码
    list match  {
        case x:List if x.isInstanceOf(List[String]) => do sth
        case x:List if x.isInstanceOf(List[Int]) => do sth else
     }
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))