JavaRush /Java Blog /Random-IT /Utilizzo di varargs quando si lavora con generici

Utilizzo di varargs quando si lavora con generici

Pubblicato nel gruppo Random-IT
Ciao! Nella lezione di oggi continueremo a studiare i farmaci generici. Si dà il caso che questo sia un argomento importante, ma non c'è nessun posto dove andare: questa è una parte estremamente importante del linguaggio :) Quando studi la documentazione Oracle sui farmaci generici o leggi le guide su Internet, ti imbatterai nei termini Tipi non riabilitabili e tipi riificabili . Che razza di parola è “Riificabile”? Anche se tutto va bene con l'inglese, è improbabile che tu l'abbia incontrato. Proviamo a tradurre! Usare varargs quando si lavora con i generici - 2
*grazie Google, mi hai aiutato molto -_-*
Un tipo riutilizzabile è un tipo le cui informazioni sono completamente disponibili in fase di esecuzione. Nel linguaggio Java, questi includono primitivi, tipi grezzi e tipi non generici. Al contrario, i tipi non riutilizzabili sono tipi le cui informazioni vengono cancellate e rese non disponibili in fase di esecuzione. Questi sono solo generici: List<String> , List<Integer> , ecc.

A proposito, ti ricordi cosa sono i vararg ?

Nel caso te ne fossi dimenticato, questi sono argomenti a lunghezza variabile. Sono utili in situazioni in cui non sappiamo esattamente quanti argomenti possono essere passati al nostro metodo. Ad esempio, se abbiamo una classe calcolatrice e ha un metodo sum. sum()Puoi passare 2 numeri, 3, 5 o quanti ne desideri al metodo . Sarebbe molto strano sovraccaricare ogni volta il metodo sum()per tenere conto di tutte le opzioni possibili. Invece possiamo fare questo:
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
Quindi, se usato varargsin combinazione con i farmaci generici, presenta alcune caratteristiche importanti. Diamo un'occhiata a questo 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>(),  //  здесь все нормально
               "Leonardo da Vinci",
               "Vasco de Gama"
       );

       // а здесь мы получаем предупреждение
       addAll(new ArrayList<Pair<String, String>>(),
               new Pair<String, String>("Leonardo", "da Vinci"),
               new Pair<String, String>("Vasco", "de Gama")
       );
   }
}
Il metodo accetta un elenco e un numero qualsiasi di oggetti addAll()come input , quindi aggiunge tutti questi oggetti all'elenco. Nel metodo chiamiamo il nostro metodo due volte . La prima volta aggiungiamo due righe regolari. Qui va tutto bene. La seconda volta aggiungiamo due oggetti . E qui improvvisamente riceviamo un avvertimento: List<E>Emain()addAll()ListListPair<String, String>

Unchecked generics array creation for varargs parameter
Cosa significa? Perché riceviamo un avviso e cosa c'entra array? Array- questo è un array e non ci sono array nel nostro codice! Cominciamo con il secondo. L'avviso menziona un array perché il compilatore converte argomenti di lunghezza variabile (varargs) in un array. In altre parole, la firma del nostro metodo è addAll():
public static <E> void addAll(List<E> list, E... array)
In realtà assomiglia a questo:
public static <E> void addAll(List<E> list, E[] array)
Cioè nel metodo main()il compilatore convertirà 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")
        }
   );
}
Va tutto bene con l'array String. Ma con un array Pair<String, String>- no. Il fatto è che Pair<String, String>questo è un tipo non reificabile. Durante la compilazione tutte le informazioni sui tipi di parametri (<String, String>) verranno cancellate. La creazione di array da tipi non riutilizzabili non è consentita in Java . Puoi verificarlo se provi a creare manualmente un array Pair<String, String>
public static void main(String[] args) {

   //  ошибка компиляции! Generic array creation
  Pair<String, String>[] array = new Pair<String, String>[10];
}
Il motivo è ovvio: la sicurezza del tipo. Come ricordi, quando crei un array, devi indicare quali oggetti (o primitive) memorizzerà questo array.
int array[] = new int[10];
In una delle lezioni precedenti abbiamo esaminato in dettaglio il meccanismo di cancellazione dei caratteri. Quindi, in questo caso, a seguito della cancellazione dei tipi, abbiamo perso l'informazione che Pairle coppie erano memorizzate nei nostri oggetti <String, String>. La creazione di un array non sarà sicura. Quando usi metodi con varargse generici, assicurati di ricordare la cancellazione dei tipi e come funziona esattamente. Se sei assolutamente sicuro del codice che hai scritto e sai che non causerà alcun problema, puoi disabilitare varargsgli avvisi ad esso associati utilizzando un'annotazione@SafeVarargs
@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 possibile problema quando si utilizzano varargsfarmaci generici insieme è l’inquinamento dell’heap. Usare varargs quando si lavora con i generici - 4La contaminazione può verificarsi nelle seguenti situazioni:
import java.util.ArrayList;
import java.util.List;

public class Main {

   static List<String> makeHeapPollution() {
       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 = makeHeapPollution();

       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 è una situazione in cui gli oggetti di tipo 1 dovrebbero trovarsi nell'heap А, ma gli oggetti di tipo finiscono lì B, a causa di errori di sicurezza del tipo. Nel nostro esempio questo è ciò che accade. Per prima cosa abbiamo creato una variabile Raw numberse le abbiamo assegnato una raccolta generica ArrayList<Number>. Successivamente abbiamo aggiunto il numero lì 1.
List<String> strings = numbers;
In questa riga, il compilatore ha cercato di avvisarci di possibili errori emettendo l'avviso “ Assegnazione non controllata... ”, ma lo abbiamo ignorato. Di conseguenza, abbiamo una variabile generica di tipo List<String>, che punta a una raccolta generica di tipo ArrayList<Number>. Questa situazione può chiaramente portare a problemi! Questo è ciò che succede. Usando la nostra nuova variabile, aggiungiamo una stringa alla raccolta. L'heap era inquinato: abbiamo aggiunto prima un numero e poi una stringa alla raccolta digitata. Il compilatore ci ha avvisato, ma noi lo abbiamo ignorato, ricevendo risultati ClassCastExceptionsolo mentre il programma era in esecuzione. Cosa c'entra varargs? L'uso varargscon farmaci generici può facilmente portare ad un inquinamento elevato. Ecco un semplice esempio:
import java.util.Arrays;
import java.util.List;

public class Main {

   static void makeHeapPollution(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");

       makeHeapPollution(cars1, cars2);
   }
}
Cosa sta succedendo qui? A causa della cancellazione del tipo, i nostri fogli parametri (li chiameremo "fogli" anziché "elenchi" per comodità) sono:
List<String>...stringsLists
- si trasformerà in un array di fogli - List[]di tipo sconosciuto (non dimenticare che varargs si trasforma in un array regolare come risultato della compilazione). Per questo motivo possiamo facilmente assegnare una variabile Object[] arraynella prima riga del metodo: i tipi sono stati cancellati dai nostri fogli! E ora abbiamo una variabile di tipo Object[], alla quale possiamo aggiungere qualsiasi cosa: tutti gli oggetti in Java vengono ereditati da Object! Al momento abbiamo solo una serie di fogli di stringhe. Ma grazie all'uso varargse alla cancellazione dei caratteri, possiamo facilmente aggiungervi un foglio di numeri, ed è ciò che facciamo. Di conseguenza, inquiniamo l’heap mescolando oggetti di diverso tipo. Il risultato sarà la stessa eccezione ClassCastExceptionquando si tenta di leggere una stringa dall'array. Uscita console:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Queste sono le conseguenze inaspettate che possono derivare dall'utilizzo di un meccanismo apparentemente semplice varargs:) Ed è qui che termina la nostra lezione di oggi. Non dimenticare di risolvere un paio di problemi e, se ti restano tempo ed energie, studia ulteriore letteratura. “ Efficace Java ” non si leggerà da solo! :) Ci vediamo!
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION