在 Scala 反射中,如何获取具体子类的泛型类型参数?
In Scala Reflection, How to get generic type parameter of a concrete subclass?
假设我有一个通用超级class:
class GenericExample[T](
a: String,
b: T
) {
def fn(i: T): T = b
}
和具体子class:
case class Example(
a: String,
b: Int
) extends GenericExample[Int](a, b)
我想通过scala反射获取函数"fn"的类型参数,所以我select并过滤它的成员:
import ScalaReflection.universe._
val baseType = typeTag[Example]
val member = baseType
.tpe
.member(methodName: TermName)
.asTerm
.alternatives
.map(_.asMethod)
.head
val paramss = member.paramss
val actualTypess: List[List[Type]] = paramss.map {
params =>
params.map {
param =>
param.typeSignature
}
}
我原以为 scala 会给我正确的结果,即 List(List(Int))
,但我只得到了通用的 List(List(T))
通过文档我发现 typeSignature 是罪魁祸首:
* This method always returns signatures in the most generic way possible, even if the underlying symbol is obtained from an
* instantiation of a generic type.
它建议我使用替代方案:
def typeSignatureIn(site: Type): Type
但是,由于 class Example 不再通用,我无法从 typeTag[Example] 获取站点,任何人都可以建议我如何仅给定 typeTag[Example] 获取 typeOf[Int] ?还是没有办法,只好恢复Java反射?
非常感谢您的帮助。
更新: 经过一些快速测试,我发现即使 MethodSymbol.returnType 也不能按预期工作,下面的代码:
member.returnType
也产生 T
,并且无法通过 asSeenFrom 更正,因为以下代码不会更改结果:
member.returnType.asSeenFrom(baseType.tpe, baseType.tpe.typeSymbol.asClass)
我可以建议两种方法:
1) 从基础 class:
中显示泛型
import scala.reflect.runtime.universe._
class GenericExample[T: TypeTag](a: String, b: T) {
def fn(i: T) = "" + b + i
}
case class Example(a: String, b: Int) extends GenericExample[Int](a, b) {}
val classType = typeOf[Example].typeSymbol.asClass
val baseClassType = typeOf[GenericExample[_]].typeSymbol.asClass
val baseType = internal.thisType(classType).baseType(baseClassType)
baseType.typeArgs.head // returns reflect.runtime.universe.Type = scala.Int
2) 添加 returns 类型的隐式方法:
import scala.reflect.runtime.universe._
class GenericExample[T](a: String, b: T) {
def fn(i: T) = "" + b + i
}
case class Example(a: String, b: Int) extends GenericExample[Int](a, b)
implicit class TypeDetector[T: TypeTag](related: GenericExample[T]) {
def getType(): Type = {
typeOf[T]
}
}
new Example("", 1).getType() // returns reflect.runtime.universe.Type = Int
我发布我的解决方案:由于 Scala 的设计,我认为没有其他选择:
Scala 反射和 Java 反射方法之间的核心区别是柯里化:Scala 方法由许多对括号组成,调用带参数的方法首先只是构造一个匿名 class 可以接受更多对括号,或者如果没有更多的括号,则构造一个 NullaryMethod class (a.k.a. call-by-name) 可以解析以产生该方法的结果。因此,当方法已经分解为方法和 NullaryMethod 签名时,scala 方法的类型仅在这一级别得到解决。
结果很明显,只能使用递归获取结果类型:
private def methodSignatureToParameter_ReturnTypes(tpe: Type): (List[List[Type]], Type) = {
tpe match {
case n: NullaryMethodType =>
Nil -> n.resultType
case m: MethodType =>
val paramTypes: List[Type] = m.params.map(_.typeSignatureIn(tpe))
val downstream = methodSignatureToParameter_ReturnTypes(m.resultType)
downstream.copy(_1 = List(paramTypes) ++ methodSignatureToParameter_ReturnTypes(m.resultType)._1)
case _ =>
Nil -> tpe
}
}
def getParameter_ReturnTypes(symbol: MethodSymbol, impl: Type) = {
val signature = symbol.typeSignatureIn(impl)
val result = methodSignatureToParameter_ReturnTypes(signature)
result
}
其中 impl
是拥有该方法的 class,symbol
是您通过 scala 反射从 Type.member(s)
获得的
假设我有一个通用超级class:
class GenericExample[T](
a: String,
b: T
) {
def fn(i: T): T = b
}
和具体子class:
case class Example(
a: String,
b: Int
) extends GenericExample[Int](a, b)
我想通过scala反射获取函数"fn"的类型参数,所以我select并过滤它的成员:
import ScalaReflection.universe._
val baseType = typeTag[Example]
val member = baseType
.tpe
.member(methodName: TermName)
.asTerm
.alternatives
.map(_.asMethod)
.head
val paramss = member.paramss
val actualTypess: List[List[Type]] = paramss.map {
params =>
params.map {
param =>
param.typeSignature
}
}
我原以为 scala 会给我正确的结果,即 List(List(Int))
,但我只得到了通用的 List(List(T))
通过文档我发现 typeSignature 是罪魁祸首:
* This method always returns signatures in the most generic way possible, even if the underlying symbol is obtained from an
* instantiation of a generic type.
它建议我使用替代方案:
def typeSignatureIn(site: Type): Type
但是,由于 class Example 不再通用,我无法从 typeTag[Example] 获取站点,任何人都可以建议我如何仅给定 typeTag[Example] 获取 typeOf[Int] ?还是没有办法,只好恢复Java反射?
非常感谢您的帮助。
更新: 经过一些快速测试,我发现即使 MethodSymbol.returnType 也不能按预期工作,下面的代码:
member.returnType
也产生 T
,并且无法通过 asSeenFrom 更正,因为以下代码不会更改结果:
member.returnType.asSeenFrom(baseType.tpe, baseType.tpe.typeSymbol.asClass)
我可以建议两种方法:
1) 从基础 class:
中显示泛型import scala.reflect.runtime.universe._
class GenericExample[T: TypeTag](a: String, b: T) {
def fn(i: T) = "" + b + i
}
case class Example(a: String, b: Int) extends GenericExample[Int](a, b) {}
val classType = typeOf[Example].typeSymbol.asClass
val baseClassType = typeOf[GenericExample[_]].typeSymbol.asClass
val baseType = internal.thisType(classType).baseType(baseClassType)
baseType.typeArgs.head // returns reflect.runtime.universe.Type = scala.Int
2) 添加 returns 类型的隐式方法:
import scala.reflect.runtime.universe._
class GenericExample[T](a: String, b: T) {
def fn(i: T) = "" + b + i
}
case class Example(a: String, b: Int) extends GenericExample[Int](a, b)
implicit class TypeDetector[T: TypeTag](related: GenericExample[T]) {
def getType(): Type = {
typeOf[T]
}
}
new Example("", 1).getType() // returns reflect.runtime.universe.Type = Int
我发布我的解决方案:由于 Scala 的设计,我认为没有其他选择:
Scala 反射和 Java 反射方法之间的核心区别是柯里化:Scala 方法由许多对括号组成,调用带参数的方法首先只是构造一个匿名 class 可以接受更多对括号,或者如果没有更多的括号,则构造一个 NullaryMethod class (a.k.a. call-by-name) 可以解析以产生该方法的结果。因此,当方法已经分解为方法和 NullaryMethod 签名时,scala 方法的类型仅在这一级别得到解决。
结果很明显,只能使用递归获取结果类型:
private def methodSignatureToParameter_ReturnTypes(tpe: Type): (List[List[Type]], Type) = {
tpe match {
case n: NullaryMethodType =>
Nil -> n.resultType
case m: MethodType =>
val paramTypes: List[Type] = m.params.map(_.typeSignatureIn(tpe))
val downstream = methodSignatureToParameter_ReturnTypes(m.resultType)
downstream.copy(_1 = List(paramTypes) ++ methodSignatureToParameter_ReturnTypes(m.resultType)._1)
case _ =>
Nil -> tpe
}
}
def getParameter_ReturnTypes(symbol: MethodSymbol, impl: Type) = {
val signature = symbol.typeSignatureIn(impl)
val result = methodSignatureToParameter_ReturnTypes(signature)
result
}
其中 impl
是拥有该方法的 class,symbol
是您通过 scala 反射从 Type.member(s)
获得的