在反射性处理Java对象时如何解决Scala类型擦除?

时间:2019-01-11 16:09:31

标签: java scala type-erasure

我有一个旧的Scala / Java混合项目。它具有一个自定义的自制ORM映射工具,该工具生成一堆Java实体类,然后在Scala中与它们一起使用,将它们从/映射到自定义属性映射和yaml文件。

今天,我偶然发现了一个问题,ORM工具将List<String>内的所有字符串都转换为Array[Byte]的映射(这导致将数据进一步转换为java.io.ByteArrayInputStream而不是简单的Scala列表。字符串)。

在尝试解决此问题并将List[String]添加到ORM字段值匹配器时,我逐字删除了。不幸的是,我无法控制自动工具生成的Java类,因此无法使用TypeTags等。反思似乎是我唯一的希望。

这是有问题的用例的简化示例。

Java实体:

public class SubEntity {
}

import java.util.ArrayList;
import java.util.List;

public class Entity {

    protected List<SubEntity> field1;
    protected List<String> field2;

    public List<SubEntity> getField1() {
        if (field1 == null) {
            field1 = new ArrayList<SubEntity>();
        }
        return this.field1;
    }

    public List<String> getField2() {
        if (field2 == null) {
            field2 = new ArrayList<String>();
        }
        return this.field2;
    }
}

一段有问题的Scala代码:

val ent = new Entity
  ent.getClass.getMethods
    .filter (m => m.getName.startsWith("get") && m.getName != "getClass")
    .filter (m => m.getParameterTypes.size == 0)
    .foreach (m => m.invoke(ent) match {
      case null => null
      // Not able to differentiate between List[String] and List[SomeEntity] and Array[Byte[
      // at runtime because of type erasure :(
      // How to solve this?
      case s: java.util.List[java.lang.String] => {
        println(s"Found list of strings in ${m.getName}, the values are ${s.toList}")
      }
      case s: java.util.List[SubEntity] => {
        println(s"Found list of SubEntity in ${m.getName}")
      }

      case _  => println(s"Unknown ${m.getName}")
  })

根据我注释掉的块,它会打印

 Found list of strings

 Found list of SubEntity

同时针对两个字段,并且无法正确确定getField1返回List[SubEntity]getField2返回List[java.lang.String]

我该如何解决问题并使匹配根据需要进行?

1 个答案:

答案 0 :(得分:0)

在以下文章的帮助下,我自己设法做到了这一点:http://tutorials.jenkov.com/java-reflection/generics.html#returntypes

我创建了一个辅助方法,用于检查返回类型的泛型并验证其是否包含请求的参数:

  import java.lang.reflect.ParameterizedType

  def doesMethodReturnGenericSubtype(objectClass: Class[_], methodName: String,
                                      typeClassToCheck: Class[_]) = {

    val method = objectClass.getMethod(methodName)
    val returnType = method.getGenericReturnType

    // assume no type known
    var typeFound = false

    if (returnType.isInstanceOf[ParameterizedType]) {
      val pType = returnType.asInstanceOf[ParameterizedType]
      val typeArguments = pType.getActualTypeArguments
      for (typeArgument <- typeArguments) {
        val typeArgClass = typeArgument.asInstanceOf[Class[_]]
        if (typeArgClass.getName == typeClassToCheck.getName)
          typeFound = true
      }
    }

    typeFound
  }

像这样使用它:

  val ent = new Entity
  ent.getClass.getMethods
    .filter (m => m.getName.startsWith("get") && m.getName != "getClass")
    .filter (m => m.getParameterTypes.size == 0)
    .foreach (m => m.invoke(ent) match {
      case null => null

      case s: java.util.Collection[_] if doesMethodReturnGenericSubtype(ent.getClass,
        m.getName, classOf[java.lang.String]) => {

        println(s"Found list of strings in ${m.getName}, the values are ${s.toList}")
      }

      case s: java.util.Collection[_] if doesMethodReturnGenericSubtype(ent.getClass,
        m.getName, classOf[SubEntity]) => {

        println(s"Found list of SubEntity in ${m.getName}")
      }

      case x  => println(s"Unknown ${m.getName} in ${x.getClass.getName}")
  })

我仍然不确定getActualTypeArguments是否能够提取出“擦除”的泛型类型,但是以某种方式可以正常工作。

相关问题