JavaRush /Blog Java /Random-PL /Używanie varargs podczas pracy z rodzajami

Używanie varargs podczas pracy z rodzajami

Opublikowano w grupie Random-PL
Cześć! Na dzisiejszej lekcji będziemy kontynuować naukę nazw generycznych. Tak się składa, że ​​jest to duży temat, ale nie ma dokąd pójść - to niezwykle ważna część języka :) Kiedy będziesz studiować dokumentację Oracle na temat leków generycznych lub czytać poradniki w Internecie, natkniesz się na terminy Typy niepodlegające refinansowaniu i typy podlegające refinansowaniu . Jakiego rodzaju słowem jest „Reableable”? Nawet jeśli z angielskim wszystko jest w porządku, jest mało prawdopodobne, że go spotkałeś. Spróbujmy przetłumaczyć! Używanie varargs podczas pracy z rodzajami generycznymi - 2
*dziękuję Google, bardzo pomogłeś -_-*
Typ reableable to typ, którego informacje są w pełni dostępne w czasie wykonywania. W języku Java są to typy pierwotne, typy surowe i typy nieogólne. Natomiast typy niepodlegające rewizji to typy, których informacje są usuwane i niedostępne w czasie wykonywania. To są po prostu typy ogólne - List<String> , List<Integer> itd.

Swoją drogą, czy pamiętasz, czym są varargi ?

Jeśli zapomniałeś, są to argumenty o zmiennej długości. Przydają się w sytuacjach, gdy nie wiemy dokładnie, ile argumentów można przekazać naszej metodzie. Na przykład, jeśli mamy klasę kalkulatora i ma ona metodę sum. Do metody sum()można przekazać 2 liczby, 3, 5 lub dowolną liczbę liczb . Byłoby bardzo dziwnie przeciążać metodę za każdym razem, sum()aby uwzględnić wszystkie możliwe opcje. 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
Zatem stosowanie varargsw połączeniu z lekami generycznymi ma kilka ważnych cech. Spójrzmy na ten 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>(),  //  здесь все нормально
               "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")
       );
   }
}
Metoda pobiera listę i dowolną liczbę obiektów addAll()jako dane wejściowe , a następnie dodaje wszystkie te obiekty do listy. W metodzie wywołujemy naszą metodę dwukrotnie . Za pierwszym razem dodajemy dwie regularne linie. Tutaj wszystko wporządku. Za drugim razem dodajemy dwa obiekty . I tu nagle dostajemy ostrzeżenie: List<E>Emain()addAll()ListListPair<String, String>

Unchecked generics array creation for varargs parameter
Co to znaczy? Dlaczego otrzymujemy ostrzeżenie i co to ma z tym wspólnego array? Array- to jest tablica, a w naszym kodzie nie ma żadnych tablic! Zacznijmy od drugiego. Ostrzeżenie wspomina o tablicy, ponieważ kompilator konwertuje argumenty o zmiennej długości (varargs) na tablicę. Innymi słowy, sygnatura naszej metody to addAll():
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 metodzie main()kompilator przekształci nasz kod w następujący sposób:
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")
        }
   );
}
Wszystko jest w porządku z tablicą String. Ale z tablicą Pair<String, String>- nie. Faktem jest, że Pair<String, String>jest to typ niepodlegający rewizji. Podczas kompilacji wszystkie informacje o typach parametrów (<String, String>) zostaną usunięte. Tworzenie tablic z typu nierewalifikowalnego nie jest dozwolone w Javie . Możesz to sprawdzić, próbując ręcznie utworzyć tablicę Para<String, String>
public static void main(String[] args) {

   //  ошибка компиляции! Generic array creation
  Pair<String, String>[] array = new Pair<String, String>[10];
}
Powód jest oczywisty – bezpieczeństwo typu. Jak pamiętasz, tworząc tablicę, musisz wskazać, jakie obiekty (lub prymitywy) ta tablica będzie przechowywać.
int array[] = new int[10];
Na jednej z poprzednich lekcji szczegółowo zbadaliśmy mechanizm usuwania czcionek. Zatem w tym przypadku w wyniku wymazywania typów straciliśmy informację, że w naszych obiektach Pairzapisane były pary <String, String>. Tworzenie tablicy będzie niebezpieczne. Używając metod z varargsrodzajami i rodzajami, pamiętaj o kasowaniu typów i dokładnym działaniu. Jeśli masz całkowitą pewność co do napisanego przez siebie kodu i wiesz, że nie spowoduje to żadnych problemów, możesz wyłączyć varargsostrzeżenia z nim związane 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 możliwym problemem w przypadku varargsjednoczesnego stosowania leków generycznych jest zanieczyszczenie sterty. Używanie varargs podczas pracy z rodzajami - 4Zanieczyszczenie może wystąpić w następujących sytuacjach:
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));
   }
}
Wyjście konsoli:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Mówiąc najprościej, zanieczyszczenie sterty to sytuacja, w której obiekty typu 1 powinny znajdować się na stercie А, ale obiekty typu tam trafiają Bz powodu błędów związanych z bezpieczeństwem typów. W naszym przykładzie tak się dzieje. Najpierw utworzyliśmy zmienną Raw numbersi przypisaliśmy jej kolekcję ogólną ArrayList<Number>. Następnie dodaliśmy tam numer 1.
List<String> strings = numbers;
W tej linii kompilator próbował ostrzec nas o możliwych błędach, wydając ostrzeżenie „ Niesprawdzone przypisanie... ”, ale zignorowaliśmy to. W rezultacie mamy zmienną ogólną typu List<String>, która wskazuje na ogólną kolekcję typu ArrayList<Number>. Taka sytuacja może wyraźnie prowadzić do kłopotów! Oto co się dzieje. Używając naszej nowej zmiennej, dodajemy ciąg do kolekcji. Sterta została zanieczyszczona - do wpisanej kolekcji dodaliśmy najpierw liczbę, a następnie ciąg znaków. Kompilator nas ostrzegł, ale zignorowaliśmy jego ostrzeżenie i otrzymaliśmy wyniki ClassCastExceptiontylko wtedy, gdy program był uruchomiony. Co to ma z tym wspólnego varargs? Używanie varargsz lekami generycznymi może łatwo doprowadzić do zanieczyszczenia sterty. Oto prosty przykład:
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);
   }
}
Co tu się dzieje? Ze względu na wymazanie typów nasze arkusze parametrów (dla wygody będziemy je nazywać „arkuszami” zamiast „listami”):
List<String>...stringsLists
- zamieni się w tablicę arkuszy - List[]nieznanego typu (pamiętaj, że varargs w wyniku kompilacji zamienia się w zwykłą tablicę). Dzięki temu Object[] arrayw pierwszej linijce metody możemy łatwo dokonać przypisania do zmiennej – typy zostały usunięte z naszych arkuszy! A teraz mamy zmienną typu Object[], do której możemy w ogóle dodać wszystko - wszystkie obiekty w Javie dziedziczą po Object! W tej chwili mamy tylko szereg arkuszy sznurków. Ale dzięki zastosowaniu varargsi wymazaniu typów możemy łatwo dodać do nich arkusz liczb, co właśnie robimy. W rezultacie zanieczyszczamy stertę, mieszając przedmioty różnych typów. Wynikiem będzie ten sam wyjątek ClassCastExceptionpodczas próby odczytania ciągu z tablicy. Wyjście konsoli:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Oto nieoczekiwane konsekwencje, jakie mogą wyniknąć z zastosowania pozornie prostego mechanizmu varargs:) I na tym kończy się nasz dzisiejszy wykład. Nie zapomnij rozwiązać kilku problemów, a jeśli masz czas i energię, przestudiuj dodatkową literaturę. „ Efektywna Java ” sama się nie przeczyta! :) Do zobaczenia!
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION