CodeGym/Java Blog/Random-IT/Digitare la cancellazione
John Squirrels
Livello 41
San Francisco

Digitare la cancellazione

Pubblicato nel gruppo Random-IT
membri
CIAO! Continuiamo la nostra serie di lezioni sui generici. In precedenza abbiamo avuto un'idea generale di cosa sono e perché sono necessari. Oggi impareremo di più su alcune delle caratteristiche dei generici e su come lavorare con loro. Andiamo! Cancellazione del tipo - 1Nell'ultima lezione abbiamo parlato della differenza tra tipi generici e tipi grezzi . Un tipo non elaborato è una classe generica il cui tipo è stato rimosso.
List list = new ArrayList();
Ecco un esempio. Qui non indichiamo che tipo di oggetti verranno inseriti nel nostro List. Se proviamo a creare un tale Liste ad aggiungervi alcuni oggetti, vedremo un avviso in IDEA:
"Unchecked call to add(E) as a member of raw type of java.util.List".
Ma abbiamo anche parlato del fatto che i generici sono apparsi solo in Java 5. Quando questa versione è stata rilasciata, i programmatori avevano già scritto un mucchio di codice utilizzando tipi non elaborati, quindi questa caratteristica del linguaggio non poteva smettere di funzionare e la capacità di creare tipi non elaborati in Java è stato preservato. Tuttavia, il problema si è rivelato più diffuso. Come sapete, il codice Java viene convertito in uno speciale formato compilato chiamato bytecode, che viene quindi eseguito dalla macchina virtuale Java. Ma se inseriamo informazioni sui parametri di tipo nel bytecode durante il processo di conversione, tutto il codice scritto in precedenza si romperebbe, perché non c'erano parametri di tipo prima di Java 5! Quando lavori con i generici, c'è un concetto molto importante che devi ricordare. Si chiama cancellazione del tipo. Significa che una classe non contiene informazioni su un parametro di tipo. Queste informazioni sono disponibili solo durante la compilazione e vengono cancellate (diventano inaccessibili) prima del runtime. Se provi a inserire il tipo errato di oggetto nel tuo List<String>, il compilatore genererà un errore. Questo è esattamente ciò che i creatori del linguaggio vogliono ottenere quando hanno creato i generici: controlli in fase di compilazione. Ma quando tutto il tuo codice Java si trasforma in bytecode, non contiene più informazioni sui parametri di tipo. In bytecode, la tua List<Cat>lista di gatti non è diversa dalle List<String>stringhe. In bytecode, nulla dice che catsè un elenco di Catoggetti. Tali informazioni vengono cancellate durante la compilazione: solo il fatto che tu abbia un List<Object> catselenco finirà nel bytecode del programma. Vediamo come funziona:
public class TestClass<T> {

   private T value1;
   private T value2;

   public void printValues() {
       System.out.println(value1);
       System.out.println(value2);
   }

   public static <T> TestClass<T> createAndAdd2Values(Object o1, Object o2) {
       TestClass<T> result = new TestClass<>();
       result.value1 = (T) o1;
       result.value2 = (T) o2;
       return result;
   }

   public static void main(String[] args) {
       Double d = 22.111;
       String s = "Test String";
       TestClass<Integer> test = createAndAdd2Values(d, s);
       test.printValues();
   }
}
Abbiamo creato la nostra TestClassclasse generica. È abbastanza semplice: in realtà è una piccola "collezione" di 2 oggetti, che vengono memorizzati immediatamente quando l'oggetto viene creato. Ha 2 Tcampi. Quando il createAndAdd2Values()metodo viene eseguito, i due oggetti passati ( Object ae Object bdevono essere convertiti al Ttipo e poi aggiunti all'oggetto TestClass. Nel main()metodo, creiamo a TestClass<Integer>, cioè l' Integerargomento di tipo sostituisce il Integerparametro di tipo. Stiamo anche passando a Doublee a Stringa il createAndAdd2Values()metodo.Pensi che il nostro programma funzionerà?Dopotutto, abbiamo specificato Integercome argomento di tipo, ma Stringsicuramente non è possibile eseguire il cast su an Integer!Eseguiamo il metodomain()metodo e controllo. Uscita console:
22.111
Test String
È stato inaspettato! Perché è successo? È il risultato della cancellazione del tipo. Le informazioni sull'argomento Integerdi tipo utilizzato per istanziare il nostro TestClass<Integer> testoggetto sono state cancellate quando il codice è stato compilato. Il campo diventa TestClass<Object> test. I nostri argomenti Doublee Stringsono stati facilmente convertiti in Objectoggetti (non vengono convertiti in Integeroggetti come ci aspettavamo!) e tranquillamente aggiunti a TestClass. Ecco un altro esempio semplice ma molto rivelatore di cancellazione del tipo:
import java.util.ArrayList;
import java.util.List;

public class Main {

   private class Cat {

   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       List<Integer> numbers = new ArrayList<>();
       List<Cat> cats = new ArrayList<>();

       System.out.println(strings.getClass() == numbers.getClass());
       System.out.println(numbers.getClass() == cats.getClass());

   }
}
Uscita console:
true
true
Sembra che abbiamo creato raccolte con tre diversi tipi di argomenti: String, Integere la nostra stessa Catclasse. Ma durante la conversione in bytecode, tutte e tre le liste diventano List<Object>, quindi quando il programma viene eseguito ci dice che stiamo usando la stessa classe in tutti e tre i casi.

Cancellazione del tipo quando si lavora con matrici e generici

C'è un punto molto importante che deve essere compreso chiaramente quando si lavora con array e classi generiche (come List). Dovresti anche tenerne conto quando scegli le strutture dati per il tuo programma. I generici sono soggetti alla cancellazione del tipo. Le informazioni sui parametri di tipo non sono disponibili in fase di esecuzione. Al contrario, gli array conoscono e possono utilizzare le informazioni sul loro tipo di dati quando il programma è in esecuzione. Il tentativo di inserire un tipo non valido in un array causerà la generazione di un'eccezione:
public class Main2 {

   public static void main(String[] args) {

       Object x[] = new String[3];
       x[0] = new Integer(222);
   }
}
Uscita console:
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
Poiché esiste una differenza così grande tra array e generici, potrebbero avere problemi di compatibilità. Soprattutto non è possibile creare un array di oggetti generici o anche solo un array parametrizzato. Ti sembra un po' confuso? Diamo un'occhiata. Ad esempio, non puoi fare nulla di tutto ciò in Java:
new List<T>[]
new List<String>[]
new T[]
Se proviamo a creare un array di List<String>oggetti, otteniamo un errore di compilazione che si lamenta della creazione di un array generico:
import java.util.List;

public class Main2 {

   public static void main(String[] args) {

       // Compilation error! Generic array creation
       List<String>[] stringLists = new List<String>[1];
   }
}
Ma perché si fa? Perché la creazione di tali array non è consentita? Questo è tutto per fornire la sicurezza del tipo. Se il compilatore ci permettesse di creare tali array di oggetti generici, potremmo creare un sacco di problemi per noi stessi. Ecco un semplice esempio tratto dal libro di Joshua Bloch "Effective Java":
public static void main(String[] args) {

   List<String>[] stringLists = new List<String>[1];  //  (1)
   List<Integer> intList = Arrays.asList(42, 65, 44);  //  (2)
   Object[] objects = stringLists;  //  (3)
   objects[0] = intList;  //  (4)
   String s = stringLists[0].get(0);  //  (5)
}
Immaginiamo che la creazione di un array come List<String>[] stringListssia consentita e non genererà un errore di compilazione. Se questo fosse vero, ecco alcune cose che potremmo fare: Nella riga 1, creiamo un array di liste: List<String>[] stringLists. Il nostro array ne contiene uno List<String>. Nella riga 2, creiamo un elenco di numeri: List<Integer>. Nella riga 3, assegniamo our List<String>[]a una Object[] objectsvariabile. Il linguaggio Java lo consente: un array di Xoggetti può memorizzare Xoggetti e oggetti di tutte le sottoclassi X. Di conseguenza, puoi inserire qualsiasi cosa in un Objectarray. Nella riga 4 sostituiamo l'unico elemento dell'array objects()(a List<String>) con a List<Integer>. Pertanto, inseriamo a List<Integer>in un array destinato esclusivamente alla memorizzazioneList<String>oggetti! Incontreremo un errore solo quando eseguiamo la riga 5. A ClassCastExceptionverrà lanciato in fase di esecuzione. Di conseguenza, a Java è stato aggiunto un divieto sulla creazione di tali array. Questo ci permette di evitare tali situazioni.

Come posso aggirare la cancellazione del tipo?

Bene, abbiamo imparato a conoscere la cancellazione dei caratteri. Proviamo a ingannare il sistema! :) Compito: abbiamo una TestClass<T>classe generica. Vogliamo scrivere un createNewT()metodo per questa classe che creerà e restituirà un nuovo Toggetto. Ma questo è impossibile, giusto? Tutte le informazioni sul Ttipo vengono cancellate durante la compilazione e in fase di esecuzione non è possibile determinare quale tipo di oggetto è necessario creare. In realtà c'è un modo complicato per farlo. Probabilmente ti ricordi che Java ha una Classclasse. Possiamo usarlo per determinare la classe di uno qualsiasi dei nostri oggetti:
public class Main2 {

   public static void main(String[] args) {

       Class classInt = Integer.class;
       Class classString = String.class;

       System.out.println(classInt);
       System.out.println(classString);
   }
}
Uscita console:
class java.lang.Integer
class java.lang.String
Ma ecco un aspetto di cui non abbiamo parlato. Nella documentazione di Oracle, vedrai che la classe Class è generica! Cancellazione del tipo - 3

https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html

La documentazione dice: "T - il tipo di classe modellato da questo oggetto Class". Traducendo questo dal linguaggio della documentazione al discorso semplice, comprendiamo che la classe dell'oggetto Integer.classnon è solo Class, ma piuttosto Class<Integer>. Il tipo dell'oggetto String.classnon è solo Class, ma piuttosto Class<String>, ecc. Se non è ancora chiaro, prova ad aggiungere un parametro di tipo all'esempio precedente:
public class Main2 {

   public static void main(String[] args) {

       Class<Integer> classInt = Integer.class;
       // Compilation error!
       Class<String> classInt2 = Integer.class;


       Class<String> classString = String.class;
       // Compilation error!
       Class<Double> classString2 = String.class;
   }
}
E ora, usando questa conoscenza, possiamo aggirare la cancellazione dei caratteri e portare a termine il nostro compito! Proviamo a ottenere informazioni su un parametro di tipo. Il nostro argomento di tipo sarà MySecretClass:
public class MySecretClass {

   public MySecretClass() {

       System.out.println("A MySecretClass object was created successfully!");
   }
}
Ed ecco come usiamo la nostra soluzione in pratica:
public class TestClass<T> {

   Class<T> typeParameterClass;

   public TestClass(Class<T> typeParameterClass) {
       this.typeParameterClass = typeParameterClass;
   }

   public T createNewT() throws IllegalAccessException, InstantiationException {
       T t = typeParameterClass.newInstance();
       return t;
   }

   public static void main(String[] args) throws InstantiationException, IllegalAccessException {

       TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
       MySecretClass secret = testString.createNewT();

   }
}
Uscita console:
A MySecretClass object was created successfully!
Abbiamo appena passato l'argomento di classe richiesto al costruttore della nostra classe generica:
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
Questo ci ha permesso di salvare le informazioni sull'argomento di tipo, impedendo che venisse completamente cancellato. Di conseguenza, siamo stati in grado di creare un fileToggetto! :) Con questo, la lezione di oggi giunge al termine. Devi sempre ricordare la cancellazione del tipo quando lavori con i generici. Questa soluzione alternativa non sembra molto conveniente, ma dovresti capire che i generici non facevano parte del linguaggio Java quando è stato creato. Questa funzionalità, che ci aiuta a creare raccolte parametrizzate e rilevare errori durante la compilazione, è stata aggiunta in seguito. In alcuni altri linguaggi che includevano generici dalla prima versione, non è prevista la cancellazione del tipo (ad esempio, in C#). A proposito, non abbiamo ancora finito di studiare i generici! Nella prossima lezione imparerai alcune altre caratteristiche dei generici. Per ora, sarebbe bene risolvere un paio di compiti! :)
Commenti
  • Popolari
  • Nuovi
  • Vecchi
Devi avere effettuato l'accesso per lasciare un commento
Questa pagina non ha ancora commenti