2016-04-03 4 views
2

Ich kam aus C++ Welt und neu zu Scala, und dieses Verhalten sieht ungewöhnlich aus.Typparameter können im Funktionskörper in Scala nicht referenziert werden?

class G1[A](val a : A) { 
    //val c:A = new A //This gives compile error 
    def fcn1(b: A): Unit = { 
    //val aobj = new A // This gives compile error 
    println(a.getClass.getSimpleName) 
    println(b.getClass.getSimpleName) 
    } 
} 

def fcnWithTP[A](): Unit = { 
    //val a = new A // This gives compile error 
    //println(a.getClass.getSimpleName) 
} 

Ich bin die Typ-Parameter in einer Klasse in einem Funktionskörper oder eine Klasse Körper nicht in der Lage ein Objekt Kiste mit. Ich kann es nur im Funktionsparameter verwenden.

Was ist der Grund dafür? Liegt das an Typauslöschung? Zur Laufzeit weiß die Funktion nicht, was der tatsächliche Typ A ist, sodass sie kein Objekt dieses Typs erstellen kann.

Was ist die allgemeine Regel dafür? Bedeutet das, dass der type-Parameter überhaupt nicht in der Funktion body oder class definition erscheinen kann? Wenn sie tatsächlich auftreten können, was sind die Beispiele?

+0

Es sieht so aus, als ob der Compiler nicht funktionieren kann. 'A' ist eine Klasse. Diese Frage sieht relevant aus: http://stackoverflow.com/questions/6200253/scala-classof-for-type-parameter (es verwendet 'classOf' statt' new' aber das Prinzip ist das gleiche, denke ich) –

+1

Und zu Beantworten Sie Ihren Hauptpunkt allgemeiner, ja, Typ-Parameter können in Funktionskörpern erscheinen, um Rückgabetypen usw. zu deklarieren, wenn Sie einen Typ benötigen, aber Sie fordern mehr in Ihrem Code: dass der Typ als Klasse verwendet wird. –

Antwort

4

Ja, Sie haben recht, dass dies auf Löschen zurückzuführen ist - Sie wissen nichts über A zur Laufzeit, dass Sie nicht explizit als Einschränkung in der Methodensignatur angegeben haben.

Typ Löschung auf der JVM nur teilweise ist, so dass Sie für die Klasse von einem Wert einige schreckliche Dinge in Scala fragen wie:

scala> List(1, 2, 3).getClass 
res0: Class[_ <: List[Int]] = class scala.collection.immutable.$colon$colon 

Sobald du mit Generika erhalten, aber alles gelöscht ist, so zum Beispiel können Sie nicht den folgenden Dinge auseinander halten:

scala> List(1, 2, 3).getClass == List("a", "b", "c").getClass 
res1: Boolean = true 

(Falls es ist nicht klar, ich denke, Typ Löschung ist eindeutig eine gute Sache, und dass das einzige Problem mit Typ Löschung auf der JVM ist, dass es ist nicht vollständiger.)

Sie können schreiben folgendes:

import scala.reflect.{ ClassTag, classTag } 

class G1[A: ClassTag](val a: A) { 
    val c: A = classTag[A].runtimeClass.newInstance().asInstanceOf[A] 
} 

Und es wie folgt verwenden:

scala> val stringG1: G1[String] = new G1("foo") 
stringG1: G1[String] = [email protected] 

scala> stringG1.c 
res2: String = "" 

Dies ist eine wirklich schlechte Idee, aber, da sie zur Laufzeit für viele, viele abstürzen Typ Parameter:

scala> class Foo(i: Int) 
defined class Foo 

scala> val fooG1: G1[Foo] = new G1(new Foo(0)) 
java.lang.InstantiationException: Foo 
    at java.lang.Class.newInstance(Class.java:427) 
    ... 43 elided 
Caused by: java.lang.NoSuchMethodException: Foo.<init>() 
    at java.lang.Class.getConstructor0(Class.java:3082) 
    at java.lang.Class.newInstance(Class.java:412) 
    ... 43 more 

Ein besserer Ansatz ist im Konstruktor übergeben:

class G1[A](val a: A)(empty:() => A) { 
    val c: A = empty() 
} 

Und ein viel besserer Ansatz ist es, eine Typklasse zu verwenden:

trait Empty[A] { 
    def default: A 
} 

object Empty { 
    def instance[A](a: => A): Empty[A] = new Empty[A] { 
    def default: A = a 
    } 

    implicit val stringEmpty: Empty[String] = instance("") 
    implicit val fooEmpty: Empty[Foo] = instance(new Foo(0)) 
} 

class G1[A: Empty](val a: A) { 
    val c: A = implicitly[Empty[A]].default 
} 

Und dann:

scala> val fooG1: G1[Foo] = new G1(new Foo(10101)) 
fooG1: G1[Foo] = [email protected] 

scala> fooG1.c 
res0: Foo = [email protected] 

Hier wir mit Bezug auf A in der Definition von G1 , aber wir verweisen nur auf Eigenschaften und Operationen, die wir zur Zeit der Kompilierung bestätigt oder für verfügbar gehalten haben.

1

Nun der Compiler weiß nicht, wie Sie eine Instanz des Typs A erstellen. Sie müssen entweder eine Factory-Funktion bereitstellen, die die Instanz A zurückgibt, oder Manifest verwenden, wodurch die Instanz A aus der Reflektion erstellt wird.

Mit Fabrik-Funktion:

class G1[A](val a:A)(f:() => A) { 
    val c:A = f() 
} 

Mit Manifest:

class G1[A](val a: A)(implicit m: scala.reflect.Manifest[A]) { 
    val c: A = m.erasure.newInstance.asInstanceOf[A] 
} 

Wenn Typ-Parameter verwendet wird, in der Regel werden Sie mehr Details über die Art A angeben, es sei denn, Sie irgendeine Art von Containern sind die Umsetzung für A, die nicht direkt mit A interagieren. Wenn Sie mit A interagieren müssen, benötigen Sie einige Angaben dazu. Man kann sagen, A eine Unterklasse von B

class G1[A <: B](val a : A) 

Jetzt Compiler würde wissen A eine Unterklasse von B ist sein muss, so können Sie alle Funktionen in B auf a:A definiert nennen.

+0

Es ist erwähnenswert, dass 'Manifest' zwar nicht offiziell veraltet ist (seit Version 2.10.0 wurde eine Verwarnungszeile angegeben, aber auskommentiert wurde), sie wird gerade durch' ClassTag' ersetzt, und im Allgemeinen Wenn 'ClassTag' funktioniert (wie hier), gibt es gute Gründe, es zu bevorzugen. –

4

Generika sind nicht dasselbe wie Vorlagen. In C++ Foo<Bar> und Foo<Bat> sind zwei verschiedene Klassen, zur Kompilierungszeit generiert. In Scala oder Java ist Foo[T] eine einzelne Klasse mit einem Typparameter. Bedenken Sie:

class Foo(val bar) 
    class Bar[T] { 
    val foo = new T // if this was possible ... 
    } 

    new Bar[Foo] 

In C++ (ein Äquivalent) würde dies nicht kompilieren, weil es keine zugänglichen Konstruktor Foo ist, die keine Argumente annimmt. Der Compiler würde das wissen, wenn er versucht, eine Vorlage für Bar<Foo> Klasse zu instanziieren, und fehlschlagen.

In Scala gibt es keine separate Klasse für Bar [Foo], so dass der Compiler zu Kompilierungszeit nichts über T weiß, außer dass es ein Typ ist. Es hat keine Möglichkeit zu wissen, ob das Aufrufen eines Konstruktors (oder einer anderen Methode) möglich oder sinnvoll ist (Sie können z. B. eine Eigenschaft oder eine abstrakte Klasse nicht instanziieren), so dass new T in diesem Kontext fehlschlagen muss: it macht einfach keinen Sinn.

Grob gesagt, können Sie Typparameter an Orten, wo jeder Typ verwendet werden kann (mit dem Rückgabetyp zum Beispiel erklären, oder eine Variable), aber wenn Sie versuchen, etwas zu tun, die nur für arbeiten einig Typen, und nicht für andere, müssen Sie Ihren Typ param spezifischer machen. Zum Beispiel, dies: def foo[T](t: T) = t.intValue funktioniert nicht, aber das: def foo[T <: Number](t: T) = t.intValue tut.

Verwandte Themen