2016-07-08 6 views
9

Ich studiere Generika in dieser Zeit und heute habe ich dieses Geheimnis für mich gefunden.Wie wirkt sich die Java-Löschung auf die generischen Arrays aus?

Lassen Sie uns die folgende Dummy-Klasse betrachten:

public class Main{ 

    public static void main(String[] args) { 
     Container<Integer> c = new Container<Integer>(); 

     c.getArray();      //No Exception 
     //c.getArray().getClass();   //Exception 
     //int a = c.getArray().length;  //Exception 

    } 

} 


class Container<T> { 

    T[] array; 

    @SuppressWarnings("unchecked") 
    Container() { 
     array = (T[])new Object[1]; 
    } 

    void put(T item) { 
     array[0] = item; 
    } 

    T get() { return array[0]; } 

    T[] getArray() { return array; } 
} 

Aufgrund der Löschung, zur Laufzeit, die T [] Rückgabetyp der getArray() -Methode wird in ein Objekt einge [], was völlig in Ordnung ist mich.

Wenn wir auf diese Methode zugreifen, wie sie ist (c.getArray()), werden keine Ausnahmen ausgelöst, aber wenn wir versuchen, einige Methoden für das zurückgegebene Array aufzurufen, zum Beispiel c.Array(). GetClass(), oder ., wenn wir versuchen, auf ein Feld zuzugreifen, zum Beispiel c.getArray() Länge, dann wird die folgende Ausnahme ausgelöst:

Exception in thread "main" java.lang.ClassCastException: [Ljava.lang. Objekt; kann nicht in [Ljava.lang.Integer;

Warum wird diese Ausnahme ausgelöst? Warum wird es nicht auch für den einfachen Aufruf c.getArray() geworfen? Warum versucht es, zu Integer [] zu konvertieren, wenn wir getClass() aufrufen oder auf die Länge zugreifen? Sind getClass() und Länge nicht auch für Objekt [] verfügbar?

Vielen Dank im Voraus für Ihre vielen (hoffe ich) und erklärenden (ich hoffe dies auch) Antworten.

+0

Um den Wert von 'c.getArray()' zu dereferenzieren, muss ein Verweis darauf temporär im Stack gespeichert werden. Ich kann mir vorstellen, dass JLS sagt - irgendwo, immer noch -, dass diese temporäre Variable überprüft werden muss, um zu sehen, ob es der ungelöschte Typ ist (da Sie den ungelöschten Typ dort kennen). –

+1

In der Tat funktioniert es, wenn Sie die Dereferenzierung in einer Methode wie 'statische void foo (Container c) {c.getArray(). GetClass(); } ' –

+0

Es gibt einen interessanten Unterschied im Bytecode, wenn Sie' 'in' 'ändern: mit' Integer' gibt es eine 'checkcast'-Anweisung (der Grund für die' ClassCastException'); mit 'Object' wird keine' checkcast' Anweisung hinzugefügt. Verständlich, da alle 'T []' in 'Object []' umgewandelt werden können; nur ein wenig überraschend, dass es zu unterschiedlichen Bytecode führt. –

Antwort

1

Wenn Sie eine unsichere ungeprüfte Besetzung ausführen, kann dies zu einer Ausnahme führen. Es ist nicht garantiert, dass Sie irgendwo eine Ausnahme bekommen.

In diesem Fall hängt es davon ab, ob der Compiler eine Umwandlung in den gelöschten Code eingefügt hat, um das Ergebnis des Aufrufs in Integer[] zu konvertieren. In diesem Fall scheint eine Besetzung in den zweiten und dritten Fall eingefügt worden zu sein, aber nicht in den ersten Fall.

In jedem der drei Fälle wird der Compiler eine Besetzung (weil es erlaubt ist, einfügen darf davon ausgehen, dass das Ergebnis Integer[] oder nicht legen Sie eine Besetzung ist (weil der Ausdruck in einer solchen Art und Weise verwendet wird, die Object[] erfordert nur in allen drei Fällen. Ob man eine Umwandlung einfügt oder nicht, hängt von der jeweiligen Compiler-Implementierung ab.

Warum würde dieser Compiler keinen Cast im ersten Fall einfügen und einen Cast im zweiten und dritten Fall einfügen? Eine offensichtliche Erklärung wäre, dass im ersten Fall das Ergebnis offensichtlich ungenutzt ist, so dass es sehr einfach ist festzustellen, dass eine Umwandlung unnötig ist. Um festzustellen, dass eine Umwandlung unnötig ist, müsste im zweiten und dritten Fall nach dem Ausdruck gesucht werden bist du? sed um zu sehen, dass es auch mit Object[] funktioniert; und das ist eine ziemlich komplizierte Analyse. Die Verfasser des Compilers haben sich wahrscheinlich für einen einfachen Ansatz entschieden, bei dem sie die Besetzung nur überspringen, wenn das Ergebnis nicht verwendet wird.

Ein anderer Compiler könnte in allen drei Fällen Umwandlungen einfügen. Und ein anderer Compiler könnte in allen drei Fällen keine Umwandlungen haben. Du kannst dich nicht darauf verlassen.

2

Der Grund für die Ausnahme ist, dass der Compiler eine Integer[] erwartet aber eine Object[] empfängt. Es wurde eine Laufzeitbesetzung hinzugefügt - auf den Aufrufseiten von getArray. Diese Modelle haben den Lügen-, Dummy-, No-Effect-Cast in Ihrem Konstruktor entdeckt.

Damit es richtig ist, braucht man die eigentliche Klasse von T, um Instanzen zu erzeugen.

@SuppressWarnings("unchecked") 
Container(Class<T> type) { 
    array = (T[]) Array.newInstance(type, 10); 
} 


    Container<Integer> c = new Container<Integer>(Integer.class); 

    c.getArray(); 
    Class<?> t = c.getArray().getClass(); 
    System.out.println(t.getName()); 
    int a = c.getArray().length; 

Auch hier bleibt eine „unsicher“ zu gieße T[] aber dies ist unvermeidbar, da Array.newInstance eine Low-Level-Methode für n-dimensionale Arrays wie in ist:

(double[][][][][][]) Array.newInstance(double.class, 3, 3, 3, 3, 3, 6); 
+0

Aber ich kann nicht verstehen, warum es sich nicht für c.getArray() beschweren kann, aber es tut dies für c.getArray(). getClass(). Was mich verrückt macht ist, dass c.getArray() in Ordnung ist, während c.getArray(). GetClass() nicht ist.Warum beschwert sich der Compiler nur für den zweiten? Im Falle von c.getArray() sollte die nicht-arbeitende Besetzung auch passieren. – acejazz

+0

Sie weisen g.getArray() nicht zu. –

1

habe ich nicht in der Lage gewesen, finden den genauen Ort in der JLS, die sagt, das ist das Verhalten, aber ich denke, der Grund, so etwas wie diese:

der Ausdruck:

c.getArray().getClass(); 

entspricht in etwa:

Integer[] arr = (Integer[]) c.getArray(); 
arr.getClass(); 

wo die Besetzung wegen Typ Löschung hinzugefügt werden muss. Diese implizite Umwandlung fügt einen checkcast Befehl in den Bytecode ein, der mit einem ClassCastException fehlschlägt, da c.getArray() vom Typ Object[] ist.

am Bytecode der Suche nach:

static void implicit() { 
    Container<Integer> c = new Container<Integer>(); 
    c.getArray().getClass(); //Exception 
} 

static void explicit() { 
    Container<Integer> c = new Container<Integer>(); 
    Integer[] arr = (Integer[]) c.getArray(); 
    arr.getClass(); //Exception 
} 

erhalten wir:

static void implicit(); 
    Code: 
     0: new   #2     // class Container 
     3: dup 
     4: invokespecial #3     // Method Container."<init>":()V 
     7: astore_0 
     8: aload_0 
     9: invokevirtual #4     // Method Container.getArray:()[Ljava/lang/Object; 
     12: checkcast  #5     // class "[Ljava/lang/Integer;" 
     15: invokevirtual #6     // Method java/lang/Object.getClass:()Ljava/lang/Class; 
     18: pop 
     19: return 

    static void explicit(); 
    Code: 
     0: new   #2     // class Container 
     3: dup 
     4: invokespecial #3     // Method Container."<init>":()V 
     7: astore_0 
     8: aload_0 
     9: invokevirtual #4     // Method Container.getArray:()[Ljava/lang/Object; 
     12: checkcast  #5     // class "[Ljava/lang/Integer;" 
     15: checkcast  #5     // class "[Ljava/lang/Integer;" 
     18: astore_1 
     19: aload_1 
     20: invokevirtual #6     // Method java/lang/Object.getClass:()Ljava/lang/Class; 
     23: pop 
     24: return 

So ist der einzige Unterschied in der explicit Version sind die drei Anweisungen:

 15: checkcast  #5     // class "[Ljava/lang/Integer;" 
     18: astore_1 
     19: aload_1 

die es nur weil ich das explizit in einer Variablen abspeicherte, soweit ich es verstehe.

+0

Die falsche Besetzung passiert in 'Integer [] arr = (Integer []) c.getArray();', oder? Also, wenn 'c.getArray(). GetClass()' fehlschlägt, sollte 'c.getArray()' in ähnlicher Weise fehlschlagen, weil die Umwandlung vor dem 'getClass()' Aufruf ausgeführt wird. – acejazz

+1

Nein, ich denke nicht - es ist die Dereferenzierung von 'c.getArray()', die das Problem verursacht. Zum Beispiel, 'System.out.println (c.getArray())' funktioniert gut; aber das ruft die Überladung 'System.out.println (Object)' auf, die den Cast nicht erfordert. –

+0

Das ist interessant, danke. Wenn 'System.out.println (c.getArray())' funktioniert gut, weil Objekt verwendet wird, wäre nicht das gleiche für 'c.getArray(). GetClass()'? Ich meine, 'getClass()' ist eine Objektmethode, also sollte es trotzdem funktionieren. Wo liege ich falsch? – acejazz

Verwandte Themen