CodeGym /Blog Java /Random-PL /Używanie varargs podczas pracy z rodzajami
Autor
John Selawsky
Senior Java Developer and Tutor at LearningTree

Używanie varargs podczas pracy z rodzajami

Opublikowano w grupie Random-PL
Cześć! Na dzisiejszej lekcji będziemy kontynuować naukę rodzajów ogólnych. Tak się składa, że ​​jest to obszerny temat, ale nie da się go uniknąć — jest to niezwykle ważna część języka :) Kiedy studiujesz dokumentację Oracle dotyczącą generyków lub czytasz samouczki online, natkniesz się na terminy typy niereplikowalne i typy podlegające ponownemu użyciu . Typ podlegający ponownej weryfikacji to typ, dla którego informacje są w pełni dostępne w czasie wykonywania. W Javie takie typy obejmują prymitywy, typy surowe i typy nieogólne. W przeciwieństwie do tego typy, których nie można powtórzyć, to typy, których informacje są usuwane i stają się niedostępne w czasie wykonywania. Tak się składa, że ​​są to generyczne — List<String>, List<Integer>, itd.

Przy okazji, czy pamiętasz, co to jest varargs?

Jeśli zapomniałeś, jest to argument o zmiennej długości. Są przydatne w sytuacjach, gdy nie wiemy, ile argumentów można przekazać naszej metodzie. Na przykład, jeśli mamy klasę kalkulatora, która ma summetodę. Metoda sum()może otrzymać 2 liczby lub 3 lub 5 lub dowolną liczbę. Byłoby bardzo dziwne przeciążać sum()metodę dla każdej możliwej liczby argumentów. Zamiast tego możemy to zrobić:

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));
   }
}
Wyjście konsoli:

15
11
To pokazuje nam, że istnieje kilka ważnych funkcji podczas używania varargs w połączeniu z rodzajami. Spójrzmy na następujący kod:

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")
       );
   }
}
Metoda addAll()przyjmuje jako dane wejściowe a List<E>i dowolną liczbę Eobiektów, a następnie dodaje wszystkie te obiekty do listy. W main()metodzie wywołujemy naszą addAll()metodę dwukrotnie. W pierwszym przypadku dodajemy dwa zwykłe łańcuchy do pliku List. Tutaj wszystko jest w porządku. W drugim przypadku dodajemy dwa Pair<String, String>obiekty do pliku List. Ale tutaj nieoczekiwanie otrzymujemy ostrzeżenie:

Unchecked generics array creation for varargs parameter
Co to znaczy? Dlaczego otrzymujemy ostrzeżenie i dlaczego jest wzmianka o array? W końcu nasz kod nie ma array! Zacznijmy od drugiego przypadku. Ostrzeżenie wspomina o tablicy, ponieważ kompilator konwertuje argument o zmiennej długości (varargs) na tablicę. Innymi słowy, sygnatura naszej addAll()metody to:

public static <E> void addAll(List<E> list, E... array)
Właściwie wygląda to tak:

public static <E> void addAll(List<E> list, E[] array)
Oznacza to, że w main()metodzie kompilator konwertuje nasz kod na następujący:

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") 
        } 
   ); 
}
Tablica Stringjest po prostu w porządku. Ale Pair<String, String>tablica nie jest. Problem polega na tym, że Pair<String, String>jest to typ, którego nie da się naprawić. Podczas kompilacji wszystkie informacje o argumentach typu (<String, String>) są usuwane. Tworzenie tablic typu niepodlegającego ponownej weryfikacji jest niedozwolone w Javie . Możesz to zobaczyć, jeśli spróbujesz ręcznie utworzyć tablicę Pair<String, String>

public static void main(String[] args) {

   // Compilation error Generic array creation
  Pair<String, String>[] array = new Pair<String, String>[10];
}
Powód jest oczywisty: bezpieczeństwo typu. Jak zapewne pamiętasz, podczas tworzenia tablicy zdecydowanie musisz określić, które obiekty (lub prymitywy) będą przechowywane w tablicy.

int array[] = new int[10];
W jednej z naszych poprzednich lekcji szczegółowo zbadaliśmy wymazywanie czcionek. W tym przypadku wymazywanie czcionek powoduje utratę informacji, które obiekty Pairprzechowują <String, String>w parach. Tworzenie tablicy byłoby niebezpieczne. Używając metod, które zawierają varargi i generyczne, pamiętaj o wymazywaniu typów i o tym, jak to działa. Jeśli masz absolutną pewność co do napisanego kodu i wiesz, że nie spowoduje on żadnych problemów, możesz wyłączyć ostrzeżenia związane z varargs za pomocą adnotacji @SafeVarargs.

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

   for (E element : array) {
       list.add(element);
   }
}
Jeśli dodasz tę adnotację do swojej metody, ostrzeżenie, które napotkaliśmy wcześniej, nie pojawi się. Innym problemem, który może wystąpić podczas używania varargs z generykami, jest zanieczyszczenie sterty. Używanie varargs podczas pracy z generykami - 3Zanieczyszczenie sterty może wystąpić w następującej sytuacji:

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));
   }
}
Wyjście konsoli:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Mówiąc prościej, zanieczyszczenie sterty ma miejsce, gdy obiekty typu Apowinny znajdować się na stercie, ale obiekty typu Btrafiają tam z powodu błędów związanych z bezpieczeństwem typów. W naszym przykładzie tak właśnie się dzieje. Najpierw utworzyliśmy zmienną surową i przypisaliśmy jej numbersogólną kolekcję ( ). ArrayList<Number>Następnie dodaliśmy numer 1do kolekcji.

List<String> strings = numbers;
W tym wierszu kompilator próbował ostrzec nas przed możliwymi błędami, wyświetlając ostrzeżenie „ Niesprawdzone przypisanie... ”, ale zignorowaliśmy to. Otrzymujemy ogólną zmienną typu List<String>, która wskazuje na ogólną kolekcję typu ArrayList<Number>. Oczywiście taka sytuacja może prowadzić do kłopotów! I tak się dzieje. Korzystając z naszej nowej zmiennej, dodajemy ciąg znaków do kolekcji. Mamy teraz zanieczyszczenie sterty — dodaliśmy liczbę, a następnie ciąg znaków do sparametryzowanej kolekcji. Kompilator nas ostrzegł, ale zignorowaliśmy jego ostrzeżenie. W rezultacie otrzymujemy ClassCastExceptiontylko podczas działania programu. Więc co to ma wspólnego z varargs? Używanie varargs z generykami może łatwo doprowadzić do zanieczyszczenia sterty. Oto prosty przykład:

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);
   }
}
Co tu się dzieje? Ze względu na wymazanie typu, nasz argument o zmiennej długości

List<String>...stringsLists
staje się tablicą list, tj List[]. obiektów nieznanego typu (nie zapomnij, że varargs podczas kompilacji zamienia się w zwykłą tablicę). Dzięki temu możemy łatwo przypisać go do Object[] arrayzmiennej w pierwszym wierszu metody — typ obiektów na naszych listach został wymazany! I teraz mamy Object[]zmienną, do której możemy dodać wszystko, ponieważ wszystkie obiekty w Javie dziedziczą Object! Na początku mamy tylko tablicę list ciągów znaków. Ale dzięki wymazywaniu czcionek i użyciu przez nas varargów możemy łatwo dodać listę liczb, co też robimy. W rezultacie zanieczyszczamy stertę, mieszając obiekty różnych typów. Wynik będzie jeszcze inny ClassCastException, gdy spróbujemy odczytać ciąg znaków z tablicy. Wyjście konsoli:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Takie nieoczekiwane konsekwencje może wywołać użycie varargs, pozornie prostego mechanizmu :) I na tym dzisiejsza lekcja dobiega końca. Nie zapomnij rozwiązać kilku zadań, a jeśli masz czas i energię, przeczytaj dodatkowe lektury. „ Efektywna Java ” sama się nie przeczyta! :) Do następnego razu!
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION