2013-08-28 3 views
5

In Scala 2.9 si potrebbe implementare esemplificazione polimorfico comeesemplificazione polimorfico in Scala con typetag e ClassTag

def newInstance[T](implicit m: Manifest[T]) = 
    m.erasure.newInstance.asInstanceOf[T] 

ma a partire da 2,10 Manifest è stato sostituito con TypeTag, e non mi è chiaro come ottenere qualcosa simile a TypeTag. Preferirei se la versione TypeTag conservasse tutte le informazioni sul tipo disponibili.

So che quanto sopra funziona solo per tratti/classi che non richiedono argomenti di costruzione, e quindi non funziona sempre, ma funziona abbastanza bene per ciò di cui ho bisogno. Se riesco a fare meglio le nuove API di reflection, sarebbe fantastico.

risposta

8

TypeTag non è ancora un sostituto per Manifest perché è una parte della riflessione Scala sperimentale e instabile. Sicuramente non dovresti usarlo per la produzione fin d'ora.

Per il caso d'uso che ha mostrato, in cui è necessario unica classe runtime (non informazioni di tipo completo con i generici, ecc), Scala 2.10 introdotte ClassTag, che è possibile utilizzare in questo modo:

def newInstance[T: ClassTag] = 
    implicitly[ClassTag[T]].runtimeClass.newInstance.asInstanceOf[T] 

o:

def newInstance[T](implicit ct: ClassTag[T]) = 
    ct.runtimeClass.newInstance.asInstanceOf[T] 

In ogni caso, Manifest non è ancora deprecato, quindi suppongo che tu possa ancora usarlo.

EDIT:

Utilizzando TypeTag per ottenere lo stesso:

import scala.reflect.runtime.universe._ 

def newInstance[T: TypeTag] = { 
    val clazz = typeTag[T].mirror.runtimeClass(typeOf[T]) 
    clazz.newInstance.asInstanceOf[T] 
} 

La soluzione di cui sopra utilizza ancora qualche riflessione Java. Se vogliamo essere purista e utilizzare solo Scala di riflessione, questa è la soluzione:

def newInstance[T: TypeTag]: T = { 
    val tpe = typeOf[T] 

    def fail = throw new IllegalArgumentException(s"Cannot instantiate $tpe") 

    val noArgConstructor = tpe.member(nme.CONSTRUCTOR) match { 
    case symbol: TermSymbol => 
     symbol.alternatives.collectFirst { 
     case constr: MethodSymbol if constr.paramss == Nil || constr.paramss == List(Nil) => constr 
     } getOrElse fail 

    case NoSymbol => fail 
    } 
    val classMirror = typeTag[T].mirror.reflectClass(tpe.typeSymbol.asClass) 
    classMirror.reflectConstructor(noArgConstructor).apply().asInstanceOf[T] 
} 
+0

Grazie @ghik, sarei ancora interessati a scoprire come farlo con typetag, preferibilmente conservando informazioni complete tipo, vale a dire l'inferenza di tipo dovrebbe dedurre 'newInstance [MyClass {Int]]: MyClass [Int] ' –

+0

@DanielMahler Ho aggiunto la soluzione basata su 'TypeTag'. Si prega di consultare la mia modifica. – ghik

+0

@ghik quando si utilizza 'runtimeMirror (this.getClass.getClassLoader)' è possibile ottenere un'eccezione quando 'T' non è caricato con lo stesso classloader di' this.type'. Dovresti usare un mirror collegato a 'TypeTag':' typeTag [T] .mirror'. –

2

Se si desidera supportare args che passa, ecco un trucco che faccio con 2.11: l'utilizzo

def newInstance[T : ClassTag](init_args: AnyRef*): T = { 
classTag[T].runtimeClass.getConstructors.head.newInstance(init_args: _*).asInstanceOf[T] 
} 

Esempio:

scala> case class A(x:Double, y:Int) 
defined class A 
scala> newInstance[A](4.5.asInstanceOf[Object],3.asInstanceOf[Object]) 
res1: A = A(4.5,3)