2013-08-28 4 views
5

In Scala 2.9 konnte man polymorphe Instanziierung alsPolymorphe Instanziierung in Scala mit TypeTag und ClassTag

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

implementieren aber ab 2.10 Manifest wird mit TypeTag, ersetzt, und es ist mir nicht klar, wie etwas zu erreichen ähnlich mit TypeTag. Ich würde es vorziehen, wenn die TypeTag-Version alle verfügbaren Informationen erhalten würde.

Ich weiß, dass das obige nur für Merkmale/Klassen funktioniert, die keine Konstruktorargumente benötigen, und dann funktioniert es nicht immer, aber es funktioniert gut genug für das, was ich brauche. Wenn ich die neuen Reflektions-APIs besser machen könnte, wäre das großartig.

Antwort

8

TypeTag ist noch kein Ersatz für Manifest, weil es ein Teil der experimentellen und instabilen Scala-Reflexion ist. Sie sollten es auf keinen Fall für die Produktion verwenden.

Für den Anwendungsfall Sie zeigte, wo nur Laufzeitklasse (nicht vollständige Typinformationen mit Generika etc.), Scala 2.10 eingeführt ClassTag benötigt, die Sie so verwenden können:

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

oder:

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

Wie auch immer, Manifest ist noch nicht veraltet, also denke ich, dass Sie es immer noch verwenden können.

EDIT:

TypeTag Unter Verwendung derselben zu erreichen:

import scala.reflect.runtime.universe._ 

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

Die obige Lösung verwendet immer noch einige Java Reflexion. Wenn wir puristisch sein wollen und nur Scala Reflexion verwenden, ist dies die Lösung:

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

Dank @ghik, habe ich noch herausfinden, wäre daran interessiert, wie es zu tun mit TypeTag, vorzugsweise während vollständige Typinformationen zu bewahren, sollte also Typinferenz schließen 'newInstance [MyClass {Int]]: MyClass [Int] ' –

+0

@DanielMahler Ich habe 'TypeTag'-basierte Lösung hinzugefügt. Bitte sehen Sie meine Bearbeitung. – ghik

+0

@ghik Wenn Sie 'runtimeMirror (this.getClass.getClassLoader) 'verwenden, können Sie eine Ausnahme erhalten, wenn' T' nicht mit dem gleichen Klassenlader wie 'this.type' geladen ist. Sie sollten einen Mirror verwenden, der an 'TypeTag' angehängt ist:' typeTag [T] .mirror'. –

2

Wenn Sie vorbei args unterstützen möchten, hier ist ein Trick, den ich mit 2,11 zu tun:

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

Beispiel Nutzung:

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)