2016-07-08 63 views
9

Sto studiando i generici in questo periodo e oggi ho trovato questo mistero per me.In che modo la cancellazione di Java influisce sugli array generici?

Consideriamo la seguente classe dummy:

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; } 
} 

causa di cancellazione, in fase di esecuzione, il [] tipo di ritorno T del metodo GetArray() viene trasformato in un oggetto [], che è completamente ragionevole me.

Se si accede a tale metodo così com'è (c.getArray()) non vengono generate eccezioni, ma se proviamo a chiamare alcuni metodi sull'array restituito, ad esempio c.Array(). GetClass() o . se si cerca di accedere ad un campo, per esempio c.getArray() lunghezza, quindi la seguente eccezione viene generata:

eccezione in filetto java.lang.ClassCastException "main": [Ljava.lang. Oggetto; non può essere lanciato su [Ljava.lang.Integer;

Perché viene generata questa eccezione? Perché non viene generato anche per la semplice chiamata c.getArray()? Perché sta provando a trasmettere a Integer [] se stiamo semplicemente chiamando getClass() o accedendo alla lunghezza? Sono getClass() e lunghezza non disponibili anche per Object []?

Grazie in anticipo per le vostre molte (spero) ed esplicativo (spero anche questo) risposte.

+0

Per dereferenziare il valore di 'c.getArray()', un riferimento ad esso deve essere temporaneamente memorizzato nello stack. Posso immaginare che JLS dica - da qualche parte, ancora guardando - che questa variabile temporanea debba essere controllata per vedere se è il tipo non cancellato (dato che si conosce il tipo non registrato lì). –

+1

Infatti, funziona se si esegue il dereferenziamento in un metodo come 'static void foo (Container c) {c.getArray(). GetClass(); } ' –

+0

C'è una differenza interessante nel bytecode se si cambia' 'in' ': con' Integer', c'è un'istruzione 'checkcast' (il motivo per' ClassCastException'); con 'Object', non viene aggiunta alcuna istruzione' checkcast'. Comprensibile, dato che tutto il 'T []' può essere lanciato su 'Object []'; appena sorprendentemente che si traduce in un bytecode diverso. –

risposta

1

Quando si esegue una trasmissione non controllata non sicura, può o non può causare un'eccezione da qualche parte. Non ti è garantito di ottenere un'eccezione da qualche parte.

In questo caso, se si ottiene un'eccezione dipende dal fatto che il compilatore abbia inserito un cast nel codice cancellato per trasmettere il risultato della chiamata a Integer[]. In questo caso, sembra che un cast sia stato inserito nel secondo e nel terzo caso, ma non nel primo caso.

In ognuno dei tre casi, il compilatore può inserire un cast (poiché è consentito assumere che il risultato sia Integer[] o non inserire un cast (poiché l'espressione viene utilizzata in modo tale da richiedere solo Object[] in tutti e tre i casi.) decidere se inserire o meno un cast è

Perché questo compilatore non inserisce un cast nel primo caso e inserisce un cast nel secondo e nel terzo caso? la spiegazione ovvia sarebbe che nel primo caso il risultato è ovviamente inutilizzato, quindi è molto semplice determinare che un cast non è necessario. Nel secondo e nel terzo caso, per determinare che un cast non è necessario richiederebbe vedere come l'espressione sei tu sed a vedere che funzionerà anche con Object[]; e questa è un'analisi piuttosto complicata. Gli autori del compilatore probabilmente optarono per un approccio semplice in cui saltano il cast solo quando il risultato non è utilizzato.

Un altro compilatore potrebbe inserire i cast in tutti e tre i casi. E un altro compilatore potrebbe non avere cast in tutti e tre i casi. Non puoi fare affidamento su di esso.

2

Il motivo dell'eccezione è che il compilatore si aspetta un Integer[] ma riceve uno Object[]. Aggiunta di un cast run-time - nei siti di chiamata di getArray. Quei calchi hanno scoperto il cast bugiardo, senza effetto, senza effetti nel tuo costruttore.

Per essere corretto, è necessario l'effettiva classe di T, al fine di creare istanze.

@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; 

Anche qui rimane un "pericoloso" cast a T[] ma questo è inevitabile come Array.newInstance è un metodo a basso livello per array n-dimensionali come in:

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

Ma non riesco a capire perché non si lamenta per c.getArray() ma lo fa per c.getArray(). getClass(). Quello che mi fa impazzire è che c.getArray() va bene mentre c.getArray(). GetClass() non lo è.Perché il compilatore si lamenta solo per il secondo? In caso di c.getArray() dovrebbe verificarsi anche il cast non funzionante. – acejazz

+0

Non si assegna g.getArray(). –

1

non ho potuto trovare il luogo esatto nel JLS, che dice che questo è il comportamento, ma penso che il motivo è qualcosa di simile:

l'espressione:

c.getArray().getClass(); 

è approssimativamente equivalente a:

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

dove il cast deve essere aggiunto perché di tipo cancellazione. Questo cast implicito aggiunge un'istruzione checkcast nel bytecode, che non riesce con uno ClassCastException, poiché c.getArray() è di tipo Object[].

Guardando il bytecode:

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 
} 

otteniamo:

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 

Così l'unica differenza nella versione explicit sono le tre istruzioni:

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

che sono lì solo per aver memorizzato esplicitamente questo in una variabile, per quanto ho capito.

+0

Il cast sbagliato si verifica in 'Integer [] arr = (Integer []) c.getArray();', giusto? Quindi, se 'c.getArray(). GetClass()' fallisce, 'c.getArray()' dovrebbe fallire in modo simile poiché il cast viene eseguito prima dell'invocazione 'getClass()'. – acejazz

+1

No, non la penso così - è il dereferenziamento di 'c.getArray()' che sembra causare il problema. Ad esempio, 'System.out.println (c.getArray())' funziona correttamente; ma sta invocando il sovraccarico 'System.out.println (Object)', che non richiede il cast. –

+0

È interessante, grazie. Se 'System.out.println (c.getArray())' sta funzionando bene perché sta usando Object, non sarebbe lo stesso per 'c.getArray(). GetClass()'? Voglio dire, 'getClass()' è un metodo Object, quindi dovrebbe funzionare comunque. Dove mi sbaglio? – acejazz