首页 文章

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

提问于
浏览
353

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?!

为什么会这样,我该如何解决它?

11 回答

  • 14

    此答案使用Manifest-API,自Scala 2.10起不推荐使用 . 请参阅以下答案以获取更多当前解决方案

    Scala是使用Type Erasure定义的,因为与Java不同,Java虚拟机(JVM)没有得到泛型 . 这意味着,在运行时,只存在类,而不是类型参数 . 在该示例中,JVM知道它正在处理 scala.collection.immutable.List ,但不是该列表使用 Int 参数化 .

    幸运的是,Scala中有一个功能可以让你解决这个问题 . 这是 Manifest . 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” . Manifest是一个类,其实例代表Scala类型 . 这些对象比JVM具有更多信息,这使我们能够测试完整的参数化类型 .

    但请注意, Manifest 仍然是一个不断发展的功能 . 作为其局限性的一个例子,它目前对方差一无所知,并假设一切都是共变的 . 一旦目前正在开发的Scala反射库完成,我预计它将变得更加稳定和稳定 .

  • -15

    你可以使用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!")
    }
    

    只要您不希望类型参数 A 本身是泛型类型,就可以使用ClassTags .

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

  • 1

    您可以使用shapeless中的 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 操作将尽可能精确地擦除 .

  • 87

    我提出了一个相对简单的解决方案,在有限的使用情况下就足够了,实际上包装了参数化类型,这些类型会受到可在match语句中使用的包装类中的类型擦除问题的影响 .

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

    这具有预期的输出,并将我们的case类的内容限制为所需的类型String Lists .

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

  • 9

    有一种方法可以克服Scala中的类型擦除问题 . 在Overcoming Type Erasure in matching 1和_908658中有一些解释,说明如何编写一些帮助程序来包装类型,包括Variance,以进行匹配 .

  • 6

    我找到了一个稍好的解决方法来解决这个非常棒的语言的限制 .

    在Scala中,阵列不会出现类型擦除问题 . 我认为通过一个例子来说明这一点更容易 .

    让我们说我们有一个 (Int, String) 的列表,然后以下给出了类型擦除警告

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

    要解决此问题,请首先创建一个案例类:

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

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

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

    这看起来很完美 .

    这将需要对代码进行细微更改以使用数组而不是列表,但这不应该是一个主要问题 .

    请注意,使用 case a:Array[(Int, String)] 仍会提供类型擦除警告,因此必须使用新的容器类(在此示例中为 IntString ) .

  • 14

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

  • 4

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

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

    难道不是更好,还是我错过了这里的观点?

  • 60

    不是解决方案,而是一种与之共存的方式,而不是完全在地毯下扫除它:添加 @unchecked 注释 . 看到这里 - http://www.scala-lang.org/api/current/index.html#scala.unchecked

  • 238

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

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

    使用模式匹配保护

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

相关问题