Alguma maneira de acessar o tipo de declaração de opção Scala em tempo de execução usando a reflexão?
-
27-09-2019 - |
Pergunta
Então, eu tenho uma aula de Scala que se parece com a seguinte:
class TestClass {
var value: Option[Int] = None
}
E estou abordando um problema em que tenho um valor de string e quero coagi -lo nessa opção [int] no tempo de execução usando a reflexão. Então, em outro pedaço de código (que não sabe nada sobre testclass), tenho algum código como este:
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)
}
Para fazer isso, preciso saber que o campo é uma opção e que o parâmetro de tipo da opção é int.
Quais são minhas opções para descobrir que o tipo de 'valor' é a opção [int] no tempo de execução (ou seja, usando a reflexão)?
Vi problemas semelhantes resolvidos anotando o campo, por exemplo, @OptionType (int.class). Eu preferiria uma solução que não exigisse anotações no alvo de reflexão, se possível.
Solução
É bastante Streightforward usando a API de reflexão Java 1.5:
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)
}
Outras dicas
No nível do código de bytes, o Java não tem genéricos. Os genéricos são implementados com o polimorfismo; portanto, uma vez que seu código -fonte (neste caso Scala) é compilado, os tipos genéricos desaparecem (isso é chamado tipo de apagamento ). Isso não torna possível reunir informações genéricas do tipo de tempo de execução via reflexão.
Uma solução possível-embora pouca solução alternativa seja para obter o tipo de tempo de execução de uma propriedade, você sabe que ele tem o mesmo tipo que o parâmetro genérico. Para instâncias de opção, podemos usar get
membro
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
}
}
class TestClass {
var value: Option[Int] = None
// ...
def doSomething {
value match {
case Some(i) => // i is an Int here
case None =>
// No other possibilities
}
}
}
O problema é que a JVM implementa genéricos através do apagamento do tipo. Portanto, é impossível descobrir através da reflexão que o tipo de value
é Option[Int]
Porque no tempo de execução, na verdade não é: é apenas Option
!
Em 2.8, você deve ser capaz de usar Manifests
assim:
var value: Option[Int] = None
val valueManifest = implicitly[scala.reflect.Manifest[Option[Int]]]
valueManifest.typeArguments // returns Some(List(Int))