CodeGym /Java Blog /Random-IT /Utilizzo di varargs quando si lavora con i generici
John Squirrels
Livello 41
San Francisco

Utilizzo di varargs quando si lavora con i generici

Pubblicato nel gruppo Random-IT
CIAO! Nella lezione di oggi continueremo a studiare i generici. Si dà il caso che questo sia un argomento importante, ma non c'è modo di evitarlo: è una parte estremamente importante del linguaggio :) Quando studi la documentazione Oracle sui generici o leggi i tutorial online, ti imbatterai nei termini tipi non reificabili e tipi reificabili . Un tipo reificabile è un tipo per il quale le informazioni sono completamente disponibili in fase di esecuzione. In Java, tali tipi includono primitive, tipi grezzi e tipi non generici. Al contrario, i tipi non reificabili sono tipi le cui informazioni vengono cancellate e diventano inaccessibili in fase di esecuzione. Si dà il caso che questi siano generici — List<String>, List<Integer>, ecc.

A proposito, ti ricordi cos'è varargs?

Nel caso l'avessi dimenticato, questo è un argomento di lunghezza variabile. Sono utili in situazioni in cui non sappiamo quanti argomenti potrebbero essere passati al nostro metodo. Ad esempio, se abbiamo una classe calcolatrice che ha un summetodo. Il sum()metodo può ricevere 2 numeri, o 3, o 5, o quanti ne vuoi. Sarebbe molto strano sovraccaricare il sum()metodo per ogni numero possibile di argomenti. Possiamo invece fare così:

public class SimpleCalculator {

   public static int sum(int...numbers) {

       int result = 0;

       for(int i : numbers) {

           result += i;
       }

       return result;
   }

   public static void main(String[] args) {

       System.out.println(sum(1,2,3,4,5));
       System.out.println(sum(2,9));
   }
}
Uscita console:

15
11
Questo ci mostra che ci sono alcune caratteristiche importanti quando si usano varargs in combinazione con i generici. Diamo un'occhiata al seguente codice:

import javafx.util.Pair;
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static <E> void addAll(List<E> list, E... array) {

       for (E element : array) {
           list.add(element);
       }
   }

   public static void main(String[] args) {
       addAll(new ArrayList<String>(), // This is okay
               "Leonardo da Vinci",
               "Vasco de Gama"
       );

       // but here we get a warning
       addAll(new ArrayList<Pair<String, String>>(),
               new Pair<String, String>("Leonardo", "da Vinci"),
               new Pair<String, String>("Vasco", "de Gama")
       );
   }
}
Il addAll()metodo prende come input un List<E>e qualsiasi numero di Eoggetti, quindi aggiunge tutti questi oggetti all'elenco. Nel main()metodo, chiamiamo il nostro addAll()metodo due volte. Nel primo caso, aggiungiamo due stringhe ordinarie al file List. Tutto è in ordine qui. Nel secondo caso, aggiungiamo due Pair<String, String>oggetti al file List. Ma qui riceviamo inaspettatamente un avviso:

Unchecked generics array creation for varargs parameter
Che cosa significa? Perché riceviamo un avviso e perché viene menzionato un array? Dopotutto, il nostro codice non ha un array! Partiamo dal secondo caso. L'avviso menziona un array perché il compilatore converte l'argomento di lunghezza variabile (varargs) in un array. In altre parole, la firma del nostro addAll()metodo è:

public static <E> void addAll(List<E> list, E... array)
In realtà sembra così:

public static <E> void addAll(List<E> list, E[] array)
Cioè, nel main()metodo, il compilatore converte il nostro codice in questo:

public static void main(String[] args) { 
   addAll(new ArrayList<String>(), 
      new String[] { 
        "Leonardo da Vinci", 
        "Vasco de Gama" 
      } 
   ); 
   addAll(new ArrayList<Pair<String,String>>(),
        new Pair<String,String>[] { 
            new Pair<String,String>("Leonardo","da Vinci"), 
            new Pair<String,String>("Vasco","de Gama") 
        } 
   ); 
}
Un Stringarray va bene. Ma un Pair<String, String>array non lo è. Il problema è che Pair<String, String>è un tipo non reificabile. Durante la compilazione, tutte le informazioni sugli argomenti di tipo (<String, String>) vengono cancellate. La creazione di array di un tipo non reificabile non è consentita in Java . Puoi vederlo se provi a creare manualmente un array Pair<String, String>

public static void main(String[] args) {

   // Compilation error Generic array creation
  Pair<String, String>[] array = new Pair<String, String>[10];
}
Il motivo è ovvio: sicurezza del tipo. Come ricorderete, quando create un array, dovete assolutamente specificare quali oggetti (o primitivi) l'array memorizzerà.

int array[] = new int[10];
In una delle lezioni precedenti, abbiamo esaminato in dettaglio la cancellazione dei caratteri. In questo caso, la cancellazione del tipo ci fa perdere le informazioni che gli Pairoggetti memorizzano <String, String>le coppie. La creazione dell'array non sarebbe sicura. Quando si utilizzano metodi che coinvolgono varargs e generici, assicurarsi di ricordare la cancellazione del tipo e come funziona. Se sei assolutamente certo del codice che hai scritto e sai che non causerà alcun problema, puoi disattivare gli avvisi relativi a varargs utilizzando le @SafeVarargsannotazioni.

@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {

   for (E element : array) {
       list.add(element);
   }
}
Se aggiungi questa annotazione al tuo metodo, l'avviso che abbiamo riscontrato in precedenza non verrà visualizzato. Un altro problema che può verificarsi quando si utilizzano varargs con i generici è l'inquinamento da heap. Utilizzo di varargs quando si lavora con i generici - 3L'inquinamento da mucchio può verificarsi nella seguente situazione:

import java.util.ArrayList;
import java.util.List;

public class Main {

   static List<String> polluteHeap() {
       List numbers = new ArrayList<Number>();
       numbers.add(1);
       List<String> strings = numbers;
       strings.add("");
       return strings;
   }

   public static void main(String[] args) {

       List<String> stringsWithHeapPollution = polluteHeap();

       System.out.println(stringsWithHeapPollution.get(0));
   }
}
Uscita console:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
In termini semplici, l'inquinamento dell'heap si verifica quando gli oggetti di tipo Adovrebbero essere nell'heap, ma gli oggetti di tipo Bfiniscono lì a causa di errori relativi alla sicurezza del tipo. Nel nostro esempio, questo è esattamente ciò che accade. Innanzitutto, abbiamo creato la numbersvariabile non elaborata e ArrayList<Number>le abbiamo assegnato una raccolta generica ( ). Quindi abbiamo aggiunto il numero 1alla raccolta.

List<String> strings = numbers;
Su questa riga, il compilatore ha cercato di avvisarci di possibili errori emettendo l'avviso " Assegnazione non verificata... ", ma l'abbiamo ignorato. Finiamo con una variabile generica di tipo List<String>che punta a una raccolta generica di tipo ArrayList<Number>. Chiaramente, questa situazione può portare a guai! E così fa. Usando la nostra nuova variabile, aggiungiamo una stringa alla raccolta. Ora abbiamo l'inquinamento dell'heap: abbiamo aggiunto un numero e poi una stringa alla raccolta parametrizzata. Il compilatore ci ha avvertito, ma abbiamo ignorato il suo avviso. Di conseguenza, otteniamo ClassCastExceptionsolo mentre il programma è in esecuzione. Quindi cosa c'entra questo con i varargs? L'uso di varargs con generici può facilmente portare all'inquinamento da heap. Ecco un semplice esempio:

import java.util.Arrays;
import java.util.List;

public class Main {

   static void polluteHeap(List<String>... stringsLists) {
       Object[] array = stringsLists;
       List<Integer> numbersList = Arrays.asList(66,22,44,12);

       array[0] = numbersList;
       String str = stringsLists[0].get(0);
   }

   public static void main(String[] args) {

       List<String> cars1 = Arrays.asList("Ford", "Fiat", "Kia");
       List<String> cars2 = Arrays.asList("Ferrari", "Bugatti", "Zaporozhets");

       polluteHeap(cars1, cars2);
   }
}
Cosa sta succedendo qui? A causa della cancellazione del tipo, il nostro argomento a lunghezza variabile

List<String>...stringsLists
diventa un array di liste, cioè List[]di oggetti di tipo sconosciuto (non dimenticare che varargs si trasforma in un normale array durante la compilazione). Per questo motivo, possiamo facilmente assegnarlo alla Object[] arrayvariabile nella prima riga del metodo: il tipo degli oggetti nelle nostre liste è stato cancellato! E ora abbiamo una Object[]variabile, alla quale possiamo aggiungere qualsiasi cosa, poiché tutti gli oggetti in Java ereditano Object! All'inizio abbiamo solo un array di elenchi di stringhe. Ma grazie alla cancellazione del tipo e al nostro uso di varargs, possiamo facilmente aggiungere un elenco di numeri, cosa che facciamo. Di conseguenza, inquiniamo l'heap mescolando oggetti di tipi diversi. Il risultato sarà ancora un altro ClassCastExceptionquando proviamo a leggere una stringa dall'array. Uscita console:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Tali conseguenze inaspettate possono essere causate dall'utilizzo di varargs, un meccanismo apparentemente semplice :) E con questo, la lezione di oggi giunge al termine. Non dimenticare di risolvere un paio di compiti e, se hai tempo ed energia, studia qualche lettura aggiuntiva. " Efficace Java " non si legge da solo! :) Fino alla prossima volta!
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION