问题 有什么方法可以在运行时使用反射访问Scala Option声明的类型?


所以,我有一个看起来像这样的Scala类:

class TestClass {
  var value: Option[Int] = None
}

我正在解决一个问题,我有一个String值,我想在运行时使用反射将其强制转换为Option [Int]。所以,在另一段代码中(对TestClass一无所知)我有一些这样的代码:

def setField[A <: Object](target: A, fieldName: String, value: String) {
  val field = target.getClass.getDeclaredField(fieldName)
  val coercedValue = ???; // How do I figure out that this needs to be Option[Int] ? 
  field.set(target, coercedValue)   
}

为此,我需要知道该字段是一个Option,并且Option的type参数是Int。

我可以选择在运行时(即使用反射)确定'value'的类型是Option [Int]?

我已经看到通过注释字段解决了类似的问题,例如@OptionType(Int.class)。如果可能的话,我更喜欢一种不需要在反射目标上进行注释的解决方案。


7088
2018-04-18 21:50


起源

我认为不需要反思。静态类型为的任何值 Option[Int] 或者是 None 要么 Some[Int]。 - Randall Schulz
嗨兰德尔。我需要使用反射,因为操作字段的类(如TestClass.value)对它正在操作的类没有编译时访问权限。我在我的问题中添加了一个示例,显示了目标对象是如何被操纵的,并突出显示了我需要回答这个问题的点。 - Graham Lea


答案:


使用Java 1.5反射API非常直观:

 def isIntOption(clasz: Class[_], propertyName: String) = {
   var result = 
     for {
       method <- cls.getMethods
       if method.getName==propertyName+"_$eq"
       param <- method.getGenericParameterTypes.toList.asInstanceOf[List[ParameterizedType]]
     } yield
       param.getActualTypeArguments.toList == List(classOf[Integer]) 
       && param.getRawType == classOf[Option[_]]
   if (result.length != 1)
     throw new Exception();
   else
     result(0)
 }

4
2018-04-19 20:06



真棒。谢谢。为了记录,我正在使用Fields,而不是Methods。我正在使用的代码最终看起来像这样: if (field.getType == classOf[Option[_]]) val optionFieldType = field.getGenericType.asInstanceOf[ParameterizedType].getActualTypeArguments()(0) - Graham Lea
Scala使用方法自动包装字段:foo for getter和foo_ $ eq for setter.So最好使用这些包装方法然后使用字段 - 以防它们在子类中被覆盖。否则,您将打破子类的预期行为。 - Alexey
我发现“GenericParameterType“的 Option[Int] 是“Object“对于我的Scala案例类(scala 2.10)。有没有办法恢复 Int 类型? - Rich


在字节码级别,Java没有Generics。泛型是用多态实现的,所以一旦编译了源代码(在本例中为Scala),泛型类型就会消失(这称为 类型擦除 )。这使得无法通过反射收集通用运行时类型信息。

一种可能 - 虽然很少脏 - 解决方法是获取属性的运行时类型,您知道它具有与Generic参数相同的类型。对于我们可以使用的Option实例 get 会员

object Test {

  def main(args : Array[String]) : Unit = {
    var option: Option[_]= None
    println(getType(option).getName)
    option = Some(1)
    println(getType(option).getName)

  }

  def getType[_](option:Option[_]):Class[_]= {
         if (option.isEmpty) classOf[Nothing] else (option.get.asInstanceOf[AnyRef]).getClass
  }
}

3
2018-04-19 10:51



谢谢你的回答 - 很多有用的信息。不幸的是,我希望这些变量的值在我想要找出类型时通常为None,这意味着该值不会帮助我获得类型。 - Graham Lea
完全没有 - 无论如何,如果vars的运行时值是None,那么你可以将它强制转换为scala.Nothing或scala.Null,因为它们分别是scala.AnyVal和scala.AnyRef的每个后代的子类。 - Miguel


class TestClass {
  var value: Option[Int] = None
// ...

  def doSomething {
    value match {
      case Some(i) => // i is an Int here
      case None =>
      // No other possibilities
    }
  }
}

2
2018-04-19 00:17



不,操作TestClass的代码是通过反射实现的 - 它在编译时不了解TestClass,需要在运行时发现类型,包括其类型参数。 - Graham Lea


问题是JVM通过类型擦除来实现泛型。所以通过反思发现它的类型是不可能的 value 是 Option[Int] 因为在运行时它实际上不是:它只是 Option

在2.8中你应该可以使用 Manifests 喜欢这个:

var value: Option[Int] = None
val valueManifest = implicitly[scala.reflect.Manifest[Option[Int]]]
valueManifest.typeArguments // returns Some(List(Int))

1
2018-04-19 10:29



Reflection知道(在运行时)函数的参数/结果正好是Option <Integer> - Alexey
但它并不知道关于价值和领域的类型,这就是问题所在。 - Alexey Romanov
Alexey是对的:正如您所说,类型参数在编译时会从签名中删除,但看起来它们仍然存在于Reflection API能够提取和使用的某种形式的字节代码中。看看答案。 - Graham Lea
好的,很高兴知道! - Alexey Romanov