2010-04-18 5 views
11

Quindi, ho una classe Scala che assomiglia a questo:Un modo per accedere al tipo di una dichiarazione di Scala Opzione in fase di runtime utilizzando la riflessione?

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

e sto affrontando un problema in cui ho un valore String e voglio costringere in che l'opzione [Int] in fase di esecuzione utilizzando la riflessione . Così, in un altro pezzo di codice (che non sa nulla TestClass) ho qualche codice come questo:

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

Per fare questo, ho bisogno di sapere che il campo è una possibilità e che il parametro tipo di opzione è Int.

Quali sono le mie opzioni per capire che il tipo di 'valore' è Opzione [Int] in fase di esecuzione (ad esempio utilizzando la riflessione)?

Ho visto problemi simili risolti annotando il campo, ad es. @OptionType (Int.class). Preferirei una soluzione che non richiedesse annotazioni sul target di riflessione, se possibile.

+1

non vedo la necessità di una riflessione. Qualsiasi valore il cui tipo statico è 'Option [Int]' può essere 'None' o 'Some [Int]'. –

+0

Ciao Randall. Ho bisogno di usare reflection perché la classe che sta manipolando i campi, come TestClass.value, non ha accesso in fase di compilazione alle classi che sta manipolando. Ho aggiunto un esempio alla mia domanda che mostra come vengono manipolati gli oggetti target e viene evidenziato il punto in cui ho bisogno di rispondere a questa domanda. –

risposta

4

E 'abbastanza streightforward utilizzando Java 1.5 riflessione 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) 
} 
+0

Eccezionale. Grazie. Per la cronaca, sto lavorando su Fields, non Methods. Il codice che sto usando ha il seguente aspetto: 'if (field.getType == classOf [Opzione [_]]) val optionFieldType = field.getGenericType.asInstanceOf [ParameterizedType] .getActualTypeArguments() (0)' –

+2

Scala esegue automaticamente il wrapping dei campi con i metodi: foo per getter e foo_ $ eq per setter. Quindi è meglio utilizzare questi metodi wrapper e quindi i campi, nel caso in cui vengano sovrascritti in sottoclasse. Altrimenti interromperà il comportamento previsto della sottoclasse. – Alexey

+3

Sto trovando che il "' GenericParameterType' "di una' Option [Int] 'è" 'Object'" per la mia classe Case Scala (scala 2.10). C'è un modo per recuperare il tipo 'Int? – 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

No, il codice che sta manipolando TestClass lo fa tramite reflection: non ha alcuna conoscenza di TestClass in fase di compilazione e deve scoprire il tipo, incluso il parametro type, in fase di esecuzione. –

1

Il problema è che JVM implementa i farmaci generici tramite la cancellazione dei tipi. Quindi è impossibile scoprire attraverso il riflesso che il tipo di value è Option[Int] perché in realtà non lo è: è solo Option!

In 2,8 si dovrebbe essere in grado di utilizzare Manifests come questo:

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

Reflection sa (in fase di esecuzione) che parametro/risultato della funzione è esattamente Opzione Alexey

+0

Ma non è a conoscenza dei tipi di valori e campi, ed è di questo che si trattava. –

+0

Alexey ha ragione: come dici tu, i parametri di tipo vengono cancellati dalla firma quando compilati, ma sembra che esistano ancora nel codice byte in qualche forma che l'API di Reflection è in grado di estrarre e utilizzare. Dai un'occhiata alla risposta. –

3

A livello di bytecode, Java non ha. I generici sono implementati con il polimorfismo, quindi una volta compilato il codice sorgente (in questo caso Scala), i tipi generici scompaiono (questo è chiamato type erasure). Ciò non rende possibile raccogliere informazioni di tipo runtime generico tramite riflessione.

Un possibile - sebbene poco sporco - soluzione è ottenere il tipo di runtime di una proprietà che si conosce ha lo stesso tipo del parametro generico. Per le istanze di opzione possiamo utilizzare 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 
    } 
} 
+0

Grazie per la tua risposta - molte informazioni utili.Sfortunatamente, mi aspetto che il valore di questi vars sia in genere pari a Nessuno al momento in cui voglio scoprire il tipo, il che significa che il valore non mi aiuterà a ottenere il tipo. –

+0

Niente affatto - Comunque, se il valore di runtime dei vars è None il più delle volte puoi costringerlo a scala.Nothing, o scala.Null poiché sono sottoclassi di ogni discendente di scala.AnyVal e scala.AnyRef rispettivamente – Miguel