2013-04-08 10 views
25

ich Java generische Funktion studierte, und ich bin nicht sicher, wie die dritte Zeile in der folgenden main Methode zu erklären:Hervorrufen von Java generische Methoden

public class Example4 { 
    public static void main(final String[] args) { 
     System.out.println(Util.<String>compare("a", "b")); 
     System.out.println(Util.<String>compare(new String(""), new Long(1))); 
     System.out.println(Util.compare(new String(""), new Long(1))); 
    } 
} 

class Util { 
    public static <T> boolean compare(T t1, T t2) { 
     return t1.equals(t2); 
    } 
} 

Die erste Zeile kompiliert, läuft und kehrt (wie erwartet) false.

Die zweite Zeile kompiliert nicht wie erwartet, da ich String und explizit mische.

Die dritte Zeile kompiliert, läuft und gibt false zurück, aber ich bin mir nicht sicher, wie es funktioniert: instanziiert der Compiler/JVM den Parametertyp T als Object? (Würde es auch eine Möglichkeit geben, diesen deklarierten Typ von T Laufzeit zu erhalten?)

Vielen Dank.

Antwort

9

Die Antwort scheint über @Teldhien und @newacct 'Antworten zu gehen. Ich war neugierig, zu "sehen" für mich den Unterschied zwischen:

System.out.println(Util.<String>compare("a", "b")); 

mit explicity Typisierung, und:

System.out.println(Util.compare(new String(""), new Long(1))); 

mit impliziter Typisierung.

Ich führte mehrere Experimente durch, wobei ich Variationen dieser beiden vorherigen Zeilen verwendete. Diese Experimente zeigen, dass der Compiler die Typen während der Kompilierung überprüft, aber die generierten Bytecodes beziehen sich nur auf Object, selbst im Fall der ersten Zeile.

Der folgende Codeabschnitt zeigt, dass Typumwandlungen selbst bei dem Explicity-Typ-Argument <String> sicher bis Object ausgeführt werden können.

public final class Example44 { 
    public static void main(final String[] args) { 
     System.out.println(new Util44<String>().compare("a", "b")); 
     System.out.println(new Util44().compare(new String(""), new Long(1))); 
    } 
} 

final class Util44<T> { 
    private T aT; 
    public boolean compare(T t1, T t2) { 
     System.out.println(this.aT); 
     // I was expecting the second and third assignments to fail 
     // with the first invocation because T is explicitly a String 
     // and then to work with the second invocation because I use 
     // a raw type and the compiler must infer a common type for T. 
     // Actually, all these assignments succeed with both invocation. 
     this.aT = (T) new String("z"); 
     this.aT = (T) new Long(0); 
     this.aT = (T) new Object(); 
     return t1.equals(t2); 
    } 
} 

Die Bytecode des main Methode wie folgt aussehen:

// Method descriptor #15 ([Ljava/lang/String;)V 
    // Stack: 7, Locals: 1 
    public static void main(java.lang.String[] args); 
    0 getstatic java.lang.System.out : java.io.PrintStream [16] 
    3 new ca.polymtl.ptidej.generics.java.Util44 [22] 
    6 dup 
    7 invokespecial ca.polymtl.ptidej.generics.java.Util44() [24] 
    10 ldc <String "a"> [25] 
    12 ldc <String "b"> [27] 
    14 invokevirtual ca.polymtl.ptidej.generics.java.Util44.compare(java.lang.Object, java.lang.Object) : boolean [29] 
    17 invokevirtual java.io.PrintStream.println(boolean) : void [33] 
    20 getstatic java.lang.System.out : java.io.PrintStream [16] 
    23 new ca.polymtl.ptidej.generics.java.Util44 [22] 
    26 dup 
    27 invokespecial ca.polymtl.ptidej.generics.java.Util44() [24] 
    30 new java.lang.String [39] 
    33 dup 
    34 ldc <String ""> [41] 
    36 invokespecial java.lang.String(java.lang.String) [43] 
    39 new java.lang.Long [46] 
    42 dup 
    43 lconst_1 
    44 invokespecial java.lang.Long(long) [48] 
    47 invokevirtual ca.polymtl.ptidej.generics.java.Util44.compare(java.lang.Object, java.lang.Object) : boolean [29] 
    50 invokevirtual java.io.PrintStream.println(boolean) : void [33] 
    53 return 
     Line numbers: 
     [pc: 0, line: 24] 
     [pc: 20, line: 25] 
     [pc: 53, line: 26] 
     Local variable table: 
     [pc: 0, pc: 54] local: args index: 0 type: java.lang.String[] 

Es macht wirklich Sinn, dass alle Anrufe mit Object als formale Parameter-Typen immer auf Methoden, wie in another question/answer erläutert. Um dies zu verdeutlichen, verwendet der Compiler immer Object für die generierten Bytecodes, egal ob es ein Argument vom Typ explility (erste Zeile) oder ein Argument impliziten Typs gibt, aber die Objekte könnten eine gemeinsame Superklasse haben, die sich von Object unterscheidet.

+1

Nur um das hier zu notieren, obwohl du es verlinkt hast; Das Phänomen wird als "Typ-Löschung" bezeichnet. –

+0

Ja. Grundsätzlich implementiert Java Generics als hinter den Kulissen Typcasting. Zum Beispiel betrachtet eine 'ArrayList ' intern jedes Element als 'Objekt', es wird jedoch automatisch wieder in' String' umgewandelt, wenn Sie es verwenden. –

20

Der gemeinsame geerbte Typ von String und Long ist Object.

Wenn Sie diese Funktion als Util.<String>compare( ausführen, erwartet der Compiler zwei String-Eingaben und gibt einen Fehler, wenn dies nicht der Fall ist. Wenn Sie es jedoch ohne <String> ausführen, wird der nächste gemeinsam genutzte vererbte Typ verwendet - in diesem Fall Object.

Wenn also compare akzeptiert t1 und t2, haben sie als Object geworfen worden, und der Code läuft gut.

Um den tatsächlichen Typ zur Laufzeit zu erhalten, verwenden Sie die gleiche Technik, die Sie mit einem anderen Objekt verwenden würden: Die getClass(), die von der Klasse Object geerbt wird.

+1

Auch für den zweiten Teil (Möglichkeit, erklärten Typ von T zur Laufzeit zu erhalten) suchen Sie nach "Generifizierte Generics" auf Google. Siehe gafter.blogspot.com/2006/11/reified-generics-for-java.html – prashant

+0

Die getClass() - Methode, die beispielsweise auf t1 angewendet wird, gibt String zurück, weil es der Laufzeittyp ist, oder? –

+0

Während ich mehr Nachforschungen anstellte, stolperte ich über diesen interessanten Blogeintrag: [super type tokens] (http://gafter.blogspot.kr/2006/12/super-type-tokens.html). –

2

Ja, Object ist eine Wahl für T, die es ermöglichen wird, zu kompilieren. Konzeptionell gibt der Compiler eine eine Art für T. Was es besonders bedeutet, spielt keine Rolle - solange es darauf schließen lässt, dass irgendein Typ für T funktioniert, dann kompiliert es. Es spielt keine Rolle, was dieser abgeleitete Typ ist, da er keine Auswirkung auf den kompilierten Code hat.