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 提供了三种主要方式来获取运行时类型信息:
- ✅ TypeTag:保留完整的类型信息。
- ✅ ClassTag:仅保留运行时类信息,不包含泛型参数。
- ✅ 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 获取。