数组和列表之间的scala类型模式匹配的差异

时间:2015-09-22 22:09:58

标签: scala pattern-matching type-erasure

我有以下陈述。

val a: Any = Array("1", "2", "3")
a match {
  case p: Array[Int] => println("int")
  case l: Array[String] => println("string")
}

val b: Any = List(1, 2, 3)
b match {
  case l: List[String] => println("string")
  case p: List[Int] => println("int")
}

关于数组编译的第一个块没有警告和输出"字符串",而关于List的第二个编译包含与类型擦除和输出相关的警告"字符串"同样。

我对JVM中的类型擦除有所了解。在运行时,JVM无法真正了解容器的泛型类型(例如List)。但是为什么Array可以在运行时避免类型擦除并获得匹配的正确类型?

我试图从scala源代码中找到答案。我发现的唯一一件事就是Array使用ClassTag但List没有。

我想了解ClassTag的工作原理。 ClassTag是类型擦除的解决方法吗?为什么像List这样的容器已经用ClassTag实现以避免类型擦除。

2 个答案:

答案 0 :(得分:4)

Scala在JVM上运行并继承其约束。 Java采用类型擦除,因此所有参数化类型在运行时都是相同的。类型信息将从中删除。这样做是为了保持与不能使用类型参数的旧Java版本的兼容性。

但是数组是Java中的特例,它们保存类型信息。所以scala数组呢。这对于在数组内部保持内存有效的未装箱值是必要的。

您应该假设在运行时期间丢失了所有类型信息。所以使用一些标签来匹配它们。

ClassTag与数据包装无关。所有类型的信息都由JVM自己提供。

Java中有自定义练习,每次遇到表达类型关系的困难时都使用AnyRef和动态转换。 Scala为静态描述类型提供了更强大的表达能力,无需运行时转换。而Scala编码风格鼓励使用重型结构来保持代码类型安全。

ClassTagTypeTag是仅可用于静态类型代码的工具。它们包含编译器在编译期间派生的类和类型信息。如果它可以静态派生类型,则可以为您提供类型标记以访问此类型。

当你编写某种类型的库并且不知道如何使用它时,这很有用。因此,您需要ClassTag作为隐式参数,并且它将由编译器根据提供给函数调用的其他参数以适当的类型填充。隐式参数由库代码按要求放置,并由调用库的外部代码自动填充。

答案 1 :(得分:0)

在这些情况下,您可能需要考虑使用shapeless获得的Typeable类型类进行类型安全转换。 E.g:

scala> import shapeless.syntax.typeable._
import shapeless.syntax.typeable._

scala> val b: Any = List(1, 2, 3)
b: Any = List(1, 2, 3)

scala> b.cast[List[String]]
res1: Option[List[String]] = None

scala> b.cast[List[Int]]
res2: Option[List[Int]] = Some(List(1, 2, 3))

正如cast[T]方法一样,通过implicits通过无形状添加到每个类型,如果转换失败,则返回Option[T],其值为None {{1}如果它成功了。

如果您愿意,可以查看Typeable的源代码。不过,我建议你在做之前先喝一杯好咖啡。 :)