Qual è il concetto di cancellazione nei generici in Java?

Qual è il concetto di cancellazione nei generici in Java?

È fondamentalmente il modo in cui i farmaci generici vengono implementati in Java tramite il compilatore. Il codice generico compilato in realtà usa solo java.lang.Object ovunque tu parli di T (o qualche altro parametro di tipo) – e ci sono alcuni metadati per dire al compilatore che è davvero un tipo generico.

Quando compilate del codice con un tipo o metodo generico, il compilatore capisce cosa intendete veramente (cioè quale sia l’argomento di tipo per T ) e verifica al momento della compilazione che state facendo la cosa giusta, ma il codice emesso di nuovo solo parla in termini di java.lang.Object – il compilatore genera caste extra dove necessario. Al momento dell’esecuzione, un List e un List sono esattamente gli stessi; le informazioni sul tipo extra sono state cancellate dal compilatore.

Confrontalo con, diciamo, C #, dove le informazioni vengono conservate al momento dell’esecuzione, consentendo al codice di contenere espressioni come typeof(T) che è l’equivalente di T.class – tranne che quest’ultimo non è valido. (Ci sono ulteriori differenze tra i generici di .NET e i generici di Java, attenzione.) La cancellazione di tipo è la fonte di molti dei messaggi di avviso / errore “dispari” quando si tratta di generici Java.

Altre risorse:

  • Documentazione Oracle
  • Wikipedia
  • Guida ai generici Java di Gilad Bracha (PDF – altamente raccomandato, potrebbe essere necessario modificare periodicamente il collegamento)
  • Domande frequenti su Java Generics di Angelika Langer

Proprio come una nota a margine, è un esercizio interessante vedere effettivamente cosa sta facendo il compilatore quando esegue la cancellazione – rende l’intero concetto un po ‘più facile da capire. C’è un flag speciale che puoi passare al compilatore per produrre file java che hanno cancellato i generici e che i cast sono stati inseriti. Un esempio:

 javac -XD-printflat -d output_dir SomeFile.java 

Il -printflat è la bandiera che viene -printflat al compilatore che genera i file. (La parte -XD è ciò che dice a javac di javac al jar eseguibile che in realtà esegue la compilazione piuttosto che solo javac , ma sto divagando …) Il -d output_dir è necessario perché il compilatore ha bisogno di un posto dove mettere il nuovo. file java.

Questo, ovviamente, fa molto più della semplice cancellazione; tutte le cose automatiche che il compilatore fa viene fatto qui. Ad esempio, vengono inseriti anche i costruttori predefiniti, i nuovi cicli foreach in stile sono espansi for loop regolari, ecc. È bello vedere le piccole cose che stanno accadendo in modo automatico.

Per completare la già completa risposta di Jon Skeet, è necessario rendersi conto che il concetto di cancellazione di tipo deriva da una necessità di compatibilità con le versioni precedenti di Java .

Presentata inizialmente a EclipseCon 2007 (non più disponibile), la compatibilità includeva quei punti:

  • Compatibilità di origine (bello avere …)
  • Compatibilità binaria (deve avere!)
  • Compatibilità di migrazione
    • I programmi esistenti devono continuare a funzionare
    • Le librerie esistenti devono essere in grado di utilizzare tipi generici
    • Deve avere!

Risposta originale:

Quindi:

 new ArrayList() => new ArrayList() 

Ci sono proposizioni per una maggiore reificazione . Reificare l’essere “considera un concetto astratto come reale”, dove i costrutti linguistici dovrebbero essere concetti, non solo zucchero sintattico.

Dovrei anche menzionare il metodo checkCollection di Java 6, che restituisce una vista dynamicmente tipologica della collezione specificata. Qualsiasi tentativo di inserire un elemento del tipo sbagliato comporterà un’immediata ClassCastException .

Il meccanismo generico nel linguaggio fornisce il controllo del tipo (statico) in fase di compilazione, ma è ansible sconfiggere questo meccanismo con i cast non controllati .

Di solito questo non è un problema, poiché il compilatore emette avvisi su tutte le operazioni non controllate.

Ci sono, tuttavia, delle volte in cui il controllo del tipo statico da solo non è sufficiente, come:

  • quando una raccolta viene passata a una libreria di terze parti ed è imperativo che il codice della libreria non corrompa la raccolta inserendo un elemento del tipo sbagliato.
  • un programma ha esito negativo con una ClassCastException , che indica che un elemento immesso in modo errato è stato inserito in una raccolta parametrizzata. Sfortunatamente, l’eccezione può verificarsi in qualsiasi momento dopo l’inserimento dell’elemento errato, pertanto in genere fornisce poche o nessuna informazione sulla reale origine del problema.

Aggiornamento luglio 2012, quasi quattro anni dopo:

È ora (2012) dettagliato in ” Regole di compatibilità della migrazione API (Signature Test) ”

Il linguaggio di programmazione Java implementa i generici mediante la cancellazione, che garantisce che le versioni legacy e generiche generino di solito file di class identici, ad eccezione di alcune informazioni ausiliarie sui tipi. La compatibilità binaria non è interrotta perché è ansible sostituire un file di class legacy con un file di class generico senza modificare o ricompilare alcun codice client.

Per facilitare l’interfaccia con codice legacy non generico, è anche ansible utilizzare la cancellazione di un tipo parametrizzato come tipo. Un tipo di questo tipo è chiamato tipo non elaborato ( Java Language Specification 3 / 4.8 ). Consentendo il tipo raw garantisce anche la retrocompatibilità per il codice sorgente.

In base a ciò, le seguenti versioni della class java.util.Iterator sono compatibili sia con codice binario che con codice sorgente a ritroso:

 Class java.util.Iterator as it is defined in Java SE version 1.4: public interface Iterator { boolean hasNext(); Object next(); void remove(); } Class java.util.Iterator as it is defined in Java SE version 5.0: public interface Iterator { boolean hasNext(); E next(); void remove(); } 

La cancellazione, letteralmente significa che l’informazione di tipo presente nel codice sorgente viene cancellata dal bytecode compilato. Cerchiamo di capirlo con un po ‘di codice.

 import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class GenericsErasure { public static void main(String args[]) { List list = new ArrayList(); list.add("Hello"); Iterator iter = list.iterator(); while(iter.hasNext()) { String s = iter.next(); System.out.println(s); } } } 

Se compilate questo codice e poi lo decompilate con un decompilatore Java, otterrete qualcosa di simile. Si noti che il codice decompilato non contiene alcuna traccia delle informazioni sul tipo presenti nel codice sorgente originale.

 import java.io.PrintStream; import java.util.*; public class GenericsErasure { public GenericsErasure() { } public static void main(String args[]) { List list = new ArrayList(); list.add("Hello"); String s; for(Iterator iter = list.iterator(); iter.hasNext(); System.out.println(s)) s = (String)iter.next(); } } 

A complemento della risposta di Jon Skeet già completata …

È stato detto che l’implementazione di farmaci generici attraverso la cancellazione porta ad alcune fastidiose limitazioni (ad esempio, nessuna new T[42] ). È stato anche detto che la ragione principale per fare le cose in questo modo era la compatibilità all’indietro nel bytecode. Questo è anche (soprattutto) vero. Il bytecode generato -target 1.5 è un po ‘diverso dalla semplice conversione di -splashed -target 1.4. Tecnicamente, è persino ansible (attraverso immensi trucchi) ottenere l’accesso a istanze di tipo generico in fase di esecuzione , dimostrando che c’è davvero qualcosa nel bytecode.

Il punto più interessante (che non è stato sollevato) è che l’implementazione di generici mediante la cancellazione offre un po ‘più di flessibilità in ciò che il sistema di tipi di alto livello può realizzare. Un buon esempio di questo sarebbe l’implementazione JVM di Scala rispetto a CLR. Sulla JVM, è ansible implementare direttamente i tipi superiori perché la stessa JVM non impone restrizioni sui tipi generici (poiché questi “tipi” sono effettivamente assenti). Ciò contrasta con il CLR, che ha conoscenza runtime delle istanze dei parametri. Per questo motivo, lo stesso CLR deve avere un concetto di come i generici dovrebbero essere usati, annullando i tentativi di estendere il sistema con regole inattese. Di conseguenza, i tipi superiori di Scala sul CLR vengono implementati utilizzando una strana forma di cancellazione emulata all’interno del compilatore stesso, rendendoli non completamente compatibili con generici .NET semplici e vecchi.

La cancellazione può essere scomoda quando si vogliono fare cose cattive in fase di esecuzione, ma offre la massima flessibilità agli scrittori di compilatori. Immagino che faccia parte del motivo per cui non se ne andrà presto.

A quanto ho capito (essendo un ragazzo di .NET ), la JVM non ha alcun concetto di generici, quindi il compilatore sostituisce i parametri di tipo con Object e esegue tutti i casting per te.

Ciò significa che i generici di Java non sono altro che lo zucchero della syntax e non offrono alcun miglioramento delle prestazioni per i tipi di valore che richiedono il boxing / unboxing quando vengono passati per riferimento.

Ci sono buone spiegazioni Aggiungo solo un esempio per mostrare come funziona la cancellazione dei caratteri con un decompilatore.

Classe originale,

 import java.util.ArrayList; import java.util.List; public class S { T obj; S(T o) { obj = o; } T getob() { return obj; } public static void main(String args[]) { List list = new ArrayList<>(); list.add("Hello"); // for-each for(String s : list) { String temp = s; System.out.println(temp); } // stream list.forEach(System.out::println); } } 

Codice decompilato dal suo bytecode,

 import java.io.PrintStream; import java.util.ArrayList; import java.util.Iterator; import java.util.Objects; import java.util.function.Consumer; public class S { Object obj; S(Object var1) { this.obj = var1; } Object getob() { return this.obj; } public static void main(String[] var0) { ArrayList var1 = new ArrayList(); var1.add("Hello"); // for-each Iterator iterator = var1.iterator(); while (iterator.hasNext()) { String string; String string2 = string = (String)iterator.next(); System.out.println(string2); } // stream PrintStream printStream = System.out; Objects.requireNonNull(printStream); var1.forEach(printStream::println); } } 

La programmazione generica è introdotta nella versione 1.5 di java
Prima di tutto cosa è generico in java?
La programmazione generica è un object di tipo sicuro, Prima di essere generico nella collezione possiamo memorizzare qualsiasi tipo di object. e dopo generico dobbiamo memorizzare i dati di un tipo specifico di object.

Quali sono i vantaggi di Generic?
I principali vantaggi del generico sono la trasmissione di tipo non richiesta e anche type-sage e Generic controllerà il tempo di compilazione. e la syntax generale di Generic è ClassOrInterface qui type è il segnale che questa class può aver impostato la class quando è stata istanziata

Esempio GenericClassDemo

genericclassDemo = new GenericClassDemo (Employee.java)