从反射获取的Java类实例中获取ClassTag

8

通过反射获取Java Class 实例,能否获得 ClassTag 信息?

这是情况。我有一个 Scala 的 case class 如下:

case class Relation[M : ClassTag](id: UUID, 
                                  model: Option[M] = None)

这是使用方式(尽管有许多相关类):

case class Organization(name: String)

case class Person(firstName: String, 
                  lastName: String,
                  organization: Relation[Organization])

我将尝试使用编程方式构建这些关系的树形结构,代码示例如下:
private def generateFieldMap(clazz: Class[_]): Map[String, Class[_]] = {
    clazz.getDeclaredFields.foldLeft(Map.empty[String, Class[_]])((map, field) => {
        map + (field.getName -> field.getType)
    })
}

private def getRelationModelClass[M : ClassTag](relationClass: Class[_ <: Relation[M]]): Class[_] = {
    classTag[M].runtimeClass
}

def treeOf[M: ClassTag](relations: List[String]): Map[String, Any] = {
    val normalizedRelations = ModelHelper.normalize(relations)
    val initialFieldMap = Map("" -> generateFieldMap(classTag[M].runtimeClass))
    val relationFieldMap = relations.foldLeft(initialFieldMap)((map, relation) => {
        val parts = relation.split('.')
        val parentRelation = parts.dropRight(1).mkString(".")
        val relationClass = map(parentRelation)(parts.last)
        val relationModelClass = relationClass match {
            case clazz: Class[_ <: Relation[_]] => getRelationModelClass(clazz)
            case _ => throw ProcessStreetException("cannot follow non-relation: " + relation)
        }
        val fieldMap = generateFieldMap(relationModelClass)
        map + (relation -> fieldMap)
    })
    relationFieldMap
}

val relations = List("organization")
val tree = treeOf[Person](relations)

这段代码无法编译。我遇到了以下错误:

[error] Foo.scala:148: not found: type _$12
[error]                 case clazz: Class[_ <: Relation[_]] => getRelationModelClass(clazz)
[error]                                   ^
[error] one error found
[error] (compile:compile) Compilation failed

基本上,我想做的是在只有Java Class时访问ClassTag信息。这可行吗?

2个回答

9
是的,这绝对是可能的,而且非常简单:
val clazz = classOf[String]
val ct = ClassTag(clazz)  // just use ClassTag.apply() method

在您的示例中,您需要像这样调用getRelationModelClass方法:
getRelationModelClass(clazz)(ClassTag(clazz))

这是可能的,因为[T:ClassTag]语法隐式创建第二个参数列表,如(implicit ct: ClassTag[T])。通常情况下,编译器会为其填充,但您也可以明确使用它。
在调用该方法时实际上不需要同时传递类和类标记。您甚至没有在方法体中使用显式类对象。只要传递类标记,就足够了。

哇,这比我之前尝试的东西简单多了。 - cdmckay
实际上这并没有帮助,因为我需要在Relation[M]中得到类型。例如,如果我执行ClassTag(clazz).runtimeClass,我会得到Relation类,但不是我想要的M类。 - cdmckay
@cdmckay,抱歉,我错过了您使用带括号的M调用classTag[M]的情况。在这种情况下,我猜你运气不好。除非您使用具体类型参数调用该方法,否则您将无法仅通过Relation的Java类检索Relation[M]中的M。这是可怕的类型擦除。 - Vladimir Matveev
@cdmckay,实际上你可以尝试使用TypeTag和Scala反射工具代替类和类标签。但是你需要将其用于所有内容。然后你就应该能够恢复泛型类型参数了。 - Vladimir Matveev
谢谢,我通过TypeTags和Scala反射API成功完成了它。 - cdmckay

1
我使用TypeTags和Scala反射API实现了我的目标。这里是必要的更改。
首先,将Relation类更改为使用TypeTag。
case class Relation[M : TypeTag](id: UUID, 
                                 model: Option[M] = None)

然后将其余的代码更改为使用Scala反射API:

private def generateFieldMap(tpe: Type): Map[String, Type] =
    tpe.members.filter(_.asTerm.isVal).foldLeft(Map.empty[String, Type])((map, field) => {
        map + (member.name.toString.trim -> member.typeSignature)
    })

private def getRelationModelType(tpe: Type): Type = 
    tpe match { case TypeRef(_, _, args) => args.head }

def treeOf[M: TypeTag](relations: List[String]): Map[String, Any] = {
    val normalizedRelations = ModelHelper.normalize(relations)
    val initialFieldMap = Map("" -> generateFieldMap(typeTag[T].tpe))
    val relationFieldMap = relations.foldLeft(initialFieldMap)((map, relation) => {
        val parts = relation.split('.')
        val parentRelation = parts.dropRight(1).mkString(".")
        val relationType = map(parentRelation)(parts.last)
        val relationModelType = getRelationModelType(relationType)
        val fieldMap = generateFieldMap(relationModelType)
        map + (relation -> fieldMap)
    })
    relationFieldMap
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接