2010-04-18 7 views
11

So habe ich eine Scala-Klasse, die wie folgt aussieht:Gibt es eine Möglichkeit, auf den Typ einer Scala Option Deklaration zur Laufzeit mit Reflektion zuzugreifen?

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

und ich bin der Bekämpfung ein Problem, wo ich einen String-Wert haben, und ich will es in diese Option [Int] zur Laufzeit zwingen, mithilfe von Reflektion . Also, in einem anderen Teil des Codes (das weiß nichts von Testclass) ich so einen Code haben:

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) 
} 

Um dies zu tun, ich muss wissen, dass das Feld eine Option ist und dass der Typ-Parameter der Option ist Int.

Welche Optionen habe ich, um herauszufinden, dass der Typ von 'Wert' zur Laufzeit Option (Int) ist (d. H. Mit Reflektion)?

Ich habe ähnliche Probleme durch Annotation des Feldes, z. @OptionType (Int.Klasse). Ich würde eine Lösung bevorzugen, die nach Möglichkeit keine Anmerkungen zum Reflexionsziel erfordert.

+1

Ich habe nicht das Bedürfnis nach Reflexion sehen. Jeder Wert, dessen statischer Typ "Option [Int]" ist, ist entweder 'None' oder' Some [Int] '. –

+0

Hallo Randall. Ich muss Reflektion verwenden, da die Klasse, die die Felder manipuliert, wie TestClass.value, keinen kompilierzeitlichen Zugriff auf die Klassen hat, die es bearbeitet. Ich habe meiner Frage ein Beispiel hinzugefügt, das zeigt, wie die Zielobjekte manipuliert werden, und den Punkt hervorgehoben, an dem ich die Antwort auf diese Frage benötige. –

Antwort

4

Es ist ziemlich verwenden streightforward Java 1.5 Reflection-API verwenden:

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) 
} 
+0

sind Genial. Vielen Dank. Für den Datensatz arbeite ich an Felder, nicht Methoden. Der Code, den ich verwende, sah folgendermaßen aus: 'if (field.getType == classOf [Option [_]]) val optionFieldType = field.getGenericType.asInstanceOf [ParameterizedType] .getActualTypeArguments() (0)' –

+2

Scala umschließt automatisch Felder mit Methoden: foo für Getter und foo_ $ eq für Setter. Es ist also besser, diese Wrapper-Methoden als Felder zu verwenden - für den Fall, dass sie in der Unterklasse überschrieben werden. Andernfalls brechen Sie das erwartete Verhalten der Unterklasse. – Alexey

+3

Ich finde, dass der "' GenericParameterType' "einer' Option [Int] 'ist" 'Object'" für meine Scala Fallklasse (Scala 2.10). Gibt es eine Möglichkeit, den Int-Typ wiederherzustellen? – Rich

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

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

Nein, der Code, der TestClass manipuliert, tut dies über Reflektion - er kennt TestClass nicht zur Kompilierzeit und muss den Typ einschließlich seines Typparameters zur Laufzeit ermitteln. –

1

Das Problem ist, dass JVM Generics durch Typ löschen implementiert. Es ist also unmöglich durch Reflektion zu entdecken, dass der Typ valueOption[Int] ist, weil es zur Laufzeit nicht wirklich ist: es ist nur Option!

In 2.8 sollten Sie in der Lage sein Manifests wie folgt zu verwenden:

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

Reflection weiß (zur Laufzeit), dass Parameter/Ergebnis der Funktion genau die Option ist Alexey

+0

Aber das weiß nicht, über Arten von Werten und Feldern, und darum ging es in der Frage. –

+0

Alexey hat Recht: Wie du sagst, werden die Typparameter beim Kompilieren aus der Signatur gelöscht, aber es scheint, dass sie in irgendeiner Form im Bytecode existieren, die das Reflection-API extrahieren und verwenden kann. Sehen Sie sich die Antwort an. –

3

auf Byte-Code-Ebene hat sich Java nicht bekam Generics. Generics werden mit Polymorphismus implementiert. Sobald Ihr Quellcode (in diesem Fall Scala) kompiliert wurde, verschwinden generische Typen (dies wird type erasure genannt). Dies macht es nicht möglich, generische Laufzeittypinformationen durch Reflektion zu sammeln.

Eine mögliche --wohl kleine schmutzige - Problemumgehung ist es, den Laufzeittyp einer Eigenschaft zu erhalten, von der Sie wissen, dass sie denselben Typ wie der Parameter Generic hat. Für Option Instanzen können wir get Mitglied

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 
    } 
} 
+0

Danke für Ihre Antwort - viele nützliche Informationen.Leider erwarte ich, dass der Wert dieser Variablen zu dem Zeitpunkt, zu dem ich den Typ herausfinden möchte, normalerweise None ist, was bedeutet, dass der Wert mir nicht hilft, den Typ zu erhalten. –

+0

Überhaupt nicht- Wie auch immer, wenn der Laufzeitwert der Vars die meiste Zeit None ist, können Sie es scala.Nothing oder scala.Null erzwingen, da sie Unterklassen von jedem Nachkommen von scala.AnyVal und scala.AnyRef bzw. – Miguel