1. 概述

在 Scala 编译过程中,泛型类型信息会在编译期被擦除,导致运行时无法获取这些信息。换句话说,像 List[Int]List[String] 在运行时会被视为相同的类型。

为了解决这个问题,Scala 提供了 TypeTag、ClassTag 和 WeakTypeTag 这些工具,用于在运行时获取被擦除的类型信息。

本文将介绍类型擦除问题,并讲解如何使用上述机制在运行时获取类型细节。

2. 类型擦除

在运行时,所有的泛型类型参数都会被擦除。也就是说,List[Int]List[String] 是无法区分的。

来看个例子:

val intList: List[Int] = List(1, 2, 3)
val strList: List[String] = List("foo", "bar")

定义一个函数来匹配类型:

def checkType[A](xs: List[A]) = xs match {
  case _: List[String] => "List of Strings"
  case _: List[Int] => "List of Ints"
}

编译时,编译器会给出警告:

On line 2: warning: non-variable type argument String in type pattern List[String] is unchecked since it is eliminated by erasure
On line 3: warning: non-variable type argument Int in type pattern List[Int] is unchecked since it is eliminated by erasure

无论传入 List[Int] 还是 List[String],都会匹配到第一个分支:

assert(checkType(intList) == "List of Strings")
assert(checkType(strList) == "List of Strings")

3. 运行时获取类型信息

Scala 提供了三种主要方式来获取运行时类型信息:

  1. TypeTag:保留完整的类型信息。
  2. ClassTag:仅保留运行时类信息,不包含泛型参数。
  3. WeakTypeTag:用于处理抽象类型。

3.1. TypeTag

TypeTag 是最强大的类型信息工具,它保留了完整的类型结构,包括嵌套泛型。

定义一个函数获取 TypeTag:

def obtainTypeTag[T](implicit tt: TypeTag[T]) = tt

调用示例:

assert(obtainTypeTag[Int].tpe == typeOf[Int])

也可以获取复杂嵌套类型:

obtainTypeTag[List[Map[Int, (Long, String)]]]

我们还可以使用 typeOf[T] 快速获取类型信息(它是 typeTag[T].tpe 的简写)。

更新之前的 checkType 方法,使用 TypeTag 来正确判断类型:

def checkType[T: TypeTag](v: T) = typeOf[T] match {
  case t if t =:= typeOf[List[String]] => "List of Strings"
  case t if t =:= typeOf[List[Int]] => "List of Ints"
}

assert(checkType(List("foo", "bar")) == "List of Strings")
assert(checkType(List(1, 2, 3)) == "List of Ints")

⚠️ 注意:=:= 用于判断类型是否完全相等,<:< 用于子类型判断。

3.2. ClassTag

ClassTag 主要用于获取运行时类信息,尤其在需要创建数组时非常关键。

比如创建 List:

def makeListFrom[T](elems: T*): List[T] = List[T](elems: _*)

✅ 没问题,因为 List 是泛型擦除的。

但如果尝试创建 Array:

def makeArrayFrom[T](elems: T*): Array[T] = Array[T](elems: _*)

❌ 就会报错:

No ClassTag available for T

这是因为 JVM 中的数组类型是具体的,Array[Int]Array[String] 是不同的类。所以需要 ClassTag 来保留类型信息。

正确写法:

def makeArrayFrom[T: ClassTag](elems: T*): Array[T] = Array[T](elems: _*)

调用:

assert(makeArrayFrom(1, 2, 3).toList == List(1, 2, 3))

3.3. WeakTypeTag

WeakTypeTag 用于处理抽象类型。如果类型是抽象的(比如 trait 中的 type 成员),TypeTag 就无法使用。

举个例子:

trait Foo {
  type Bar
  def barType = typeTag[Bar].tpe
}

❌ 编译失败:

error: No TypeTag available for Foo.this.Bar

换成 WeakTypeTag:

trait Foo {
  type Bar
  def barType = weakTypeTag[Bar].tpe
}

✅ 现在可以正常编译和运行:

assert(new Foo {
  override type Bar = Int
}.barType.toString == "Foo.this.Bar")

4. 小结

本文讲解了 Scala 中的类型擦除问题,并介绍了三种获取运行时类型信息的方法:

工具 用途
✅ TypeTag 获取完整类型信息(含泛型)
✅ ClassTag 获取运行时类信息(用于数组等)
✅ WeakTypeTag 获取抽象类型的运行时信息

这些工具在需要类型安全、反射或泛型编程时非常有用。

📌 源码可从 GitHub 获取。


原始标题:Mapping of Data Objects in Kotlin