JavaRush /Blog Java /Random-PL /Co to są leki generyczne w Javie

Co to są leki generyczne w Javie

Opublikowano w grupie Random-PL
Cześć! Dzisiaj porozmawiamy o lekach generycznych. Muszę przyznać, że dowiesz się wielu nowych rzeczy! Nie tylko ten, ale także kilka kolejnych wykładów poświęconych będzie lekom generycznym. Co to są leki generyczne w Javie — 1 Dlatego jeśli ten temat jest dla Ciebie interesujący, masz szczęście: dziś dowiesz się wiele o funkcjach leków generycznych. Cóż, jeśli nie, uspokój się i zrelaksuj! :) To bardzo ważny temat i trzeba go znać. Zacznijmy od prostego: „co” i „dlaczego”. Co to są leki generyczne? Generics to typy z parametrem. Tworząc rodzajnik, określasz nie tylko jego typ, ale także typ danych, z którymi ma pracować. Myślę, że najbardziej oczywisty przykład przyszedł Ci już do głowy - to jest ArrayList! Oto jak zwykle tworzymy to w programie:
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<String> myList1 = new ArrayList<>();
       myList1.add("Test String 1");
       myList1.add("Test String 2");
   }
}
Jak można się domyślić, osobliwością listy jest to, że nie będzie można „umieścić” w niej wszystkiego: działa wyłącznie z obiektami String. Wybierzmy się teraz na krótką wycieczkę do historii Jawy i spróbujmy odpowiedzieć na pytanie: „dlaczego?” W tym celu sami napiszemy uproszczoną wersję klasy ArrayList. Nasza lista może jedynie dodawać dane do tablicy wewnętrznej i odbierać te dane:
public class MyListClass {

   private Object[] data;
   private int count;

   public MyListClass() {
       this.data = new Object[10];
       this.count = 0;
   }

   public void add(Object o) {
       this.data[count] = o;
       count++;
   }

   public Object[] getData() {
       return data;
   }
}
Powiedzmy, że chcemy, aby nasza lista zawierała tylko liczby Integer. Nie mamy leków generycznych. Nie możemy jawnie określić instancji check Integerw pliku add(). Wtedy cała nasza klasa będzie się nadawała tylko dla Integer, a my będziemy musieli napisać tę samą klasę dla wszystkich istniejących na świecie typów danych! Decydujemy się zdać na naszych programistów i po prostu zostawić komentarz w kodzie, aby nie dodali tam niczego niepotrzebnego:
//use it ONLY with Integer data type
public void add(Object o) {
   this.data[count] = o;
   count++;
}
Jeden z programistów przeoczył ten komentarz i niechcący próbował umieścić na liście liczby zmieszane z ciągami znaków, a następnie obliczyć ich sumę:
public class Main {

   public static void main(String[] args) {

       MyListClass list = new MyListClass();
       list.add(100);
       list.add(200);
       list.add("Lolkek");
       list.add("Shalala");

       Integer sum1 = (Integer) list.getData()[0] + (Integer) list.getData()[1];
       System.out.println(sum1);

       Integer sum2 = (Integer) list.getData()[2] + (Integer) list.getData()[3];
       System.out.println(sum2);
   }
}
Dane wyjściowe konsoli: 300 Wyjątek w wątku „main” java.lang.ClassCastException: java.lang.String nie może zostać rzutowany na java.lang.Integer w Main.main(Main.java:14) Co jest najgorsze w tej sytuacji? Daleko mu do nieuwagi programisty. Najgorsze jest to, że zły kod znalazł się w ważnym miejscu naszego programu i został pomyślnie skompilowany . Teraz błąd dostrzeżemy nie na etapie kodowania, a dopiero na etapie testowania (i to w najlepszym przypadku!). Naprawianie błędów na późniejszym etapie rozwoju kosztuje znacznie więcej – zarówno pieniędzy, jak i czasu. To jest właśnie zaleta typów generycznych: klasa generyczna pozwoli pechowemu programiście natychmiast wykryć błąd. Kod po prostu się nie skompiluje!
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Integer> myList1 = new ArrayList<>();

       myList1.add(100);
       myList1.add(100);
       myList1.add("Lolkek");//błąd!
       myList1.add("Shalala");//błąd!
   }
}
Programista natychmiast „opamięta się” i natychmiast się poprawi. Nawiasem mówiąc, nie musieliśmy tworzyć własnej klasy, Listaby zobaczyć tego rodzaju błąd. Po prostu usuń nawiasy typu ( <Integer>) ze zwykłej listy ArrayList!
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

      List list = new ArrayList();

      list.add(100);
      list.add(200);
      list.add("Lolkek");
      list.add("Shalala");

       System.out.println((Integer) list.get(0) + (Integer) list.get(1));
       System.out.println((Integer) list.get(2) + (Integer) list.get(3));
   }
}
Dane wyjściowe konsoli: 300 Wyjątek w wątku „main” java.lang.ClassCastException: java.lang.String nie może zostać rzutowany na java.lang.Integer w Main.main(Main.java:16) Oznacza to, że nawet przy użyciu „natywnych” narzędzi Java, możesz popełnić ten błąd i utworzyć niebezpieczną kolekcję. Jeśli jednak wkleimy ten kod do IDEa, pojawi się ostrzeżenie: „ Niezaznaczone wywołanie add(E) jako członek typu surowego java.util.List ”. To mówi nam, że coś może pójść nie tak podczas dodawania elementu do kolekcja bez leków generycznych, nie w ten sposób. Ale co oznacza wyrażenie „typ surowy”? Dosłowne tłumaczenie będzie dość dokładne – „ typ surowy ” lub „ typ brudny ”. Raw typejest klasą ogólną, z której usunięto jej typ. Innymi słowy, List myList1jest to Raw type. Przeciwieństwem raw typejest generic typeklasa generyczna (znana również jako klasa parameterized type), utworzona poprawnie, ze specyfikacją typu. Na przykład, List<String> myList1. Możesz mieć pytanie: dlaczego w ogóle wolno używać raw types? Powód jest prosty. Twórcy Javy pozostawili wsparcie w tym języku, raw typesaby nie stwarzać problemów z kompatybilnością. Do czasu wypuszczenia Java 5.0 (w tej wersji po raz pierwszy pojawiły się typy generyczne), wiele kodu zostało już napisanych przy użyciu raw types. Dlatego taka możliwość istnieje do dziś. O klasycznej książce Joshuy Blocha „Effective Java” wspominaliśmy już nie raz na wykładach. Jako jeden z twórców języka nie pominął tematu użycia raw typesiw książce generic types. Co to są leki generyczne w Javie - 2Rozdział 23 tej książki ma bardzo wymowny tytuł: „Nie używaj typów surowych w nowym kodzie”. Warto o tym pamiętać. Używając klas ogólnych, nigdy nie zamieniaj ich generic typena raw type.

Wpisane metody

Java umożliwia wpisywanie poszczególnych metod, tworząc tzw. metody generyczne. Dlaczego takie metody są wygodne? Przede wszystkim dlatego, że pozwalają na pracę z różnymi typami parametrów. Jeśli tę samą logikę można bezpiecznie zastosować do różnych typów, metoda ogólna jest doskonałym rozwiązaniem. Spójrzmy na przykład. Powiedzmy, że mamy jakąś listę myList1. Chcemy usunąć z niego wszystkie wartości i wypełnić wszystkie wolne przestrzenie nową wartością. Tak będzie wyglądać nasza klasa z metodą ogólną:
public class TestClass {

   public static <T> void fill(List<T> list, T val) {
       for (int i = 0; i < list.size(); i++)
           list.set(i, val);
   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       strings.add("Старая строка 1");
       strings.add("Старая строка 2");
       strings.add("Старая строка 3");

       fill(strings, "Новая строка");

       System.out.println(strings);

       List<Integer> numbers = new ArrayList<>();
       numbers.add(1);
       numbers.add(2);
       numbers.add(3);

       fill(numbers, 888);
       System.out.println(numbers);
   }
}
Zwróć uwagę na składnię, wygląda to trochę nietypowo:
public static <T> void fill(List<T> list, T val)
Typ zwracany jest poprzedzony znakiem <T>, który wskazuje metodę ogólną. W tym przypadku metoda przyjmuje na wejściu 2 parametry: listę obiektów T i inny oddzielny obiekt T. Za pomocą <T> osiąga się typowanie metody: nie możemy przekazać tam listy ciągów znaków i liczby. Lista ciągów i ciąg znaków, lista liczb i liczba, lista naszych obiektów Cati kolejny obiekt Cat- to jedyny sposób. Metoda main()wyraźnie pokazuje, że metoda fill()z łatwością współpracuje z różnymi typami danych. Najpierw pobiera jako dane wejściowe listę ciągów znaków i ciąg znaków, a następnie listę liczb i liczbę. Dane wyjściowe konsoli: [Newline, Newline, Newline] [888, 888, 888] Wyobraź sobie, że fill()potrzebowalibyśmy logiki metod dla 30 różnych klas, a nie mielibyśmy metod ogólnych. Bylibyśmy zmuszeni napisać tę samą metodę 30 razy, tylko dla różnych typów danych! Ale dzięki metodom generycznym możemy ponownie wykorzystać nasz kod! :)

Wpisane klasy

Możesz nie tylko używać klas ogólnych dostępnych w Javie, ale także tworzyć własne! Oto prosty przykład:
public class Box<T> {

   private T t;

   public void set(T t) {
       this.t = t;
   }

   public T get() {
       return t;
   }

   public static void main(String[] args) {

       Box<String> stringBox = new Box<>();

       stringBox.set("Старая строка");
       System.out.println(stringBox.get());
       stringBox.set("Новая строка");

       System.out.println(stringBox.get());

       stringBox.set(12345);//ошибка компиляции!
   }
}
Nasza klasa Box<T>(„pole”) zostaje wpisana. Po przypisaniu mu podczas tworzenia typu danych ( ) <T>nie będziemy już mogli umieszczać w nim obiektów innych typów. Można to zobaczyć na przykładzie. Podczas tworzenia określiliśmy, że nasz obiekt będzie współpracował z ciągami znaków:
Box<String> stringBox = new Box<>();
A kiedy w ostatniej linijce kodu spróbujemy umieścić w ramce liczbę 12345, pojawia się błąd kompilacji! Właśnie w ten sposób stworzyliśmy własną klasę generyczną! :) Na tym zakończymy nasz dzisiejszy wykład. Ale nie żegnamy się z generykami! W kolejnych wykładach porozmawiamy o bardziej zaawansowanych funkcjach, więc nie żegnajcie się! ) Powodzenia w nauce! :)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION