代码之家  ›  专栏  ›  技术社区  ›  Daniel C. Sobral

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

  •  359
  • Daniel C. Sobral  · 技术社区  · 15 年前

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

    为什么会这样,我该如何应对呢?

    11 回复  |  直到 15 年前
        1
  •  242
  •   Community Ian Goodfellow    4 年前

    这个答案使用 Manifest -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类型的类。这些对象比JVM有更多的信息,这使我们能够测试完整的参数化类型。

    显示 这仍然是一个不断发展的特点。作为其局限性的一个例子,它目前对方差一无所知,并且假设所有事物都是协变量。我预计,一旦目前正在开发的Scala反射库完成,它将变得更加稳定和可靠。

        2
  •  102
  •   tksfz    9 年前

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

    您还可以使用ClassTag来实现这一点(这样您就不必依赖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 它本身就是泛型类型。

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

        3
  •  64
  •   squarefrog    9 年前

    你可以使用 Typeable 输入来自的类 shapeless 为了得到你想要的结果,

    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擦除 可打字 实例可用。

        4
  •  17
  •   thricejamie    13 年前

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

    这具有预期的输出,并将case类的内容限制为所需的类型String list。

    详情如下: http://www.scalafied.com/?p=60

        5
  •  13
  •   harmanjd    10 年前

    有一种方法可以克服Scala中的类型擦除问题。在里面 Overcoming Type Erasure in matching 1 Overcoming Type Erasure in Matching 2 (Variance)

        6
  •  11
  •   Jus12    13 年前

    我找到了一个稍微好一点的解决方法,解决了这一令人敬畏的语言的局限性。

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

        7
  •  6
  •   j0k BlackMario    12 年前

    因为Java不知道实际的元素类型,所以我发现只使用 List[_] . 然后警告消失了,代码描述了现实——这是一个未知事物的列表。

        8
  •  4
  •   agilesteel    13 年前

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

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

    是不是更好,还是我没有抓住重点?

        9
  •  1
  •   matanster    10 年前

    这不是一个解决方案,而是一种生活方式,而不必彻底掩盖它: @unchecked 注释。看这里- http://www.scala-lang.org/api/current/index.html#scala.unchecked

        10
  •  0
  •   Steve Robinson-Burns    6 年前

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

    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))
    
        11
  •  -20
  •   Huangmao Quan    9 年前

    使用模式匹配保护

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