De toute façon d'accéder au type d'une déclaration d'option Scala lors de l'exécution en utilisant la réflexion?
-
27-09-2019 - |
Question
Alors, j'ai une classe Scala qui ressemble à ceci:
class TestClass {
var value: Option[Int] = None
}
et je lutte contre un problème où j'ai une valeur de chaîne et je veux le contraindre dans cette option [Int] lors de l'exécution en utilisant la réflexion. Ainsi, dans un autre morceau de code (qui ne sait rien à propos TestClass) J'ai un code comme ceci:
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)
}
Pour ce faire, je dois savoir que le champ est une option et que le paramètre de type de l'option est Int.
Quelles sont mes options pour déterminer que le type de « valeur » est l'option [Int] lors de l'exécution (à savoir en utilisant la réflexion)?
J'ai vu des problèmes similaires résolus en annotant le champ, par exemple @OptionType (Int.class). Je préfère une solution qui ne nécessite pas d'annotations sur la cible de réflexion si possible.
La solution
Il est tout à fait streightforward en utilisant Java 1.5 API de réflexion:
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)
}
Autres conseils
Au niveau du code octet, Java n'a pas obtenu Generics. Génériques sont mises en œuvre avec le polymorphisme, donc une fois que votre code source (dans ce cas, Scala) est compilé, les types génériques disparaissent (ce qu'on appelle type effacement ). Cela ne fait pas possible de recueillir des informations de type d'exécution générique par réflexion.
Une possible --though peu dirty-- solution de contournement est d'obtenir le type d'exécution d'une propriété que vous savez qu'il a le même type que le paramètre générique. Pour les instances de l'option, nous pouvons utiliser membre 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
}
}
class TestClass {
var value: Option[Int] = None
// ...
def doSomething {
value match {
case Some(i) => // i is an Int here
case None =>
// No other possibilities
}
}
}
Le problème est que JVM met en œuvre des génériques par l'effacement de type. Il est donc impossible de découvrir par la réflexion que le type de value
est Option[Int]
car au moment de l'exécution, il est en fait pas: il est juste Option
2.8, vous devriez être en mesure d'utiliser Manifests
comme ceci:
var value: Option[Int] = None
val valueManifest = implicitly[scala.reflect.Manifest[Option[Int]]]
valueManifest.typeArguments // returns Some(List(Int))