Scala 3中如何在运行时保留对象声明类型以缓解类型擦除?
Scala 3中如何在运行时保留对象的声明类型以缓解类型擦除?
Scala 3完全可以实现类似Scala 2中保留运行时完整声明类型的机制,只是需要适配Scala 3全新的反射与编译时API——原来的TypeTag被拆分为scala.quoted.Type(负责携带完整编译时类型信息)和依赖Quotes上下文的获取方式,直接summon[Type[Int]]失败正是因为缺少编译时的Quotes上下文。
核心实现思路
利用Scala 3的inline方法与编译时宏能力,在编译阶段捕获完整的类型信息并绑定到包装对象上,无需依赖全局缓存(当然也可以根据需求保留缓存逻辑)。以下是适配Scala 3的Unerase实现:
import scala.collection.concurrent.TrieMap import scala.quoted.* import scala.language.implicitConversions // 包装类,直接存储值与对应的完整类型信息 case class Unerase[T](self: T, private val typeRep: Type[T]) object Unerase { // 可选:如果需要复用类型信息可以保留全局缓存(和Scala 2示例逻辑对齐) private lazy val cache = TrieMap.empty[Int, Type[_]] // 通过inline方法在编译时捕获Type[T],依赖Quotes上下文 inline def apply[T](value: T): Unerase[T] = ${ applyImpl[T]('value) } // 宏实现:在编译阶段获取Type[T]并构造Unerase实例 private def applyImpl[T: Type](value: Expr[T])(using Quotes): Expr[Unerase[T]] = { val typeRep = Type[T] // 可选:将类型信息存入缓存 cache += System.identityHashCode(value) -> typeRep '{ Unerase($value, ${ Expr(typeRep) }) } } // 获取包装对象的完整类型信息 def getType[T](unerased: Unerase[T]): Type[T] = unerased.typeRep // 隐式包装:自动将任意值转换为Unerase实例,inline确保编译时捕获类型 inline implicit def box[T](value: T): Unerase[T] = apply(value) // 隐式解包:自动将Unerase实例转换回原类型 implicit def unbox[T](unerased: Unerase[T]): T = unerased.self }
使用示例
// 自动隐式包装基础类型 val intVal: Unerase[Int] = 42 // 自动隐式包装带泛型的复杂类型 val strList: Unerase[List[String]] = List("foo", "bar") // 获取并打印完整类型信息 println(Unerase.getType(intVal)) // 输出: Int println(Unerase.getType(strList)) // 输出: List[String]
关键细节说明
inline与Quotes上下文:inline方法允许在编译阶段访问Quotes上下文,从而获取Type[T]——这是Scala 3中获取完整编译时类型信息的核心方式,替代了Scala 2中TypeTag的隐式推导逻辑。- 类型信息存储:示例中直接将
Type[T]绑定到Unerase实例,避免了Scala 2示例中全局缓存可能带来的内存泄漏或哈希冲突问题;如果需要复用类型信息,也可以保留原有的全局缓存逻辑。 - 泛型类型保留:
Type[T]会完整保留泛型参数信息(比如List[String]不会被擦除为List[_]),完全满足缓解类型擦除的需求。
额外扩展
如果需要对类型信息进行更复杂的反射操作(比如遍历泛型参数、获取注解等),可以结合Scala 3的scala.reflectAPI,将Type[T]转换为可操作的反射节点,实现类似Scala 2中TypeTag的所有功能。
内容的提问来源于stack exchange,提问作者tribbloid
相关产品推荐
相关产品推荐

