CodeGym/Blog Java/Random-PL/Generyki w Javie
Autor
Volodymyr Portianko
Java Engineer at Playtika

Generyki w Javie

Opublikowano w grupie Random-PL
Cześć! Porozmawiamy o Javie Generics. Muszę powiedzieć, że dużo się nauczysz! Nie tylko ta lekcja, ale także kilka następnych lekcji będzie poświęconych lekom generycznym. Tak więc, jeśli interesują Cię leki generyczne, dziś masz szczęśliwy dzień: dowiesz się wiele o funkcjach leków generycznych. A jeśli nie, zrezygnuj i zrelaksuj się! :) To bardzo ważny temat i trzeba go znać. Zacznijmy od prostego: „co” i „dlaczego”.

Czym są kody generyczne Java?

Rodzaje to typy, które mają parametr. Podczas tworzenia typu ogólnego określa się nie tylko typ, ale także typ danych, z którym będzie on działał. Zgaduję, że najbardziej oczywisty przykład już przyszedł Ci do głowy: ArrayList! W ten sposób zwykle tworzymy go 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ć, cechą tej listy jest to, że nie możemy upchnąć w niej wszystkiego: działa ona wyłącznie z obiektami typu String . A teraz zróbmy małą dygresję do historii Javy i spróbujmy odpowiedzieć na pytanie „dlaczego?” W tym celu napiszemy własną uproszczoną wersję klasy ArrayList. Nasza lista wie tylko, jak dodawać dane i pobierać dane z wewnętrznej tablicy:
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;
   }
}
Załóżmy, że chcemy, aby nasza lista zawierała tylko liczby całkowite . Nie używamy typu ogólnego. Nie chcemy zawierać jawnego sprawdzenia „instanceof Integer ” w metodzie add() . Gdybyśmy to zrobili, cała nasza klasa nadawałaby się tylko dla Integer , a musielibyśmy napisać podobną klasę dla każdego innego typu danych na świecie! Będziemy polegać na naszych programistach i po prostu zostawimy komentarz w kodzie, aby upewnić się, że nie dodadzą niczego, czego nie chcemy:
// Use this class ONLY with the Integer data type
public void add(Object o) {
   this.data[count] = o;
   count++;
}
Jeden z programistów przegapił ten komentarz i nieumyślnie umieścił kilka ciągów znaków na liście liczb, 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);
   }
}
Wyjście konsoli:
300
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
      at Main.main (Main.java:14)
Co jest najgorsze w tej sytuacji? Na pewno nie przez nieostrożność programisty. Najgorsze jest to, że błędny kod znalazł się w ważnym miejscu w naszym programie i pomyślnie skompilował. Teraz błąd napotkamy nie podczas pisania kodu, a jedynie podczas testowania (i to jest najlepszy scenariusz!). Naprawa błędów na późniejszych etapach rozwoju kosztuje znacznie więcej — zarówno pod względem finansowym, jak i czasowym. Właśnie w tym przypadku generyczne przynoszą nam korzyści: klasa generyczna pozwala pechowemu programiście natychmiast wykryć błąd. Program 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"); // Error!
       myList1.add("Shalala"); // Error!
   }
}
Programista natychmiast zdaje sobie sprawę ze swojego błędu i natychmiast staje się lepszy. Nawiasem mówiąc, nie musieliśmy tworzyć własnej klasy List , aby zobaczyć tego rodzaju błąd. Po prostu usuń nawiasy ostre i wpisz ( <Integer> ) ze zwykłej tablicy 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));
   }
}
Wyjście konsoli:
300
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
     at Main.main(Main.java:16)
Innymi słowy, nawet używając „natywnych” mechanizmów Javy, możemy popełnić tego rodzaju błąd i stworzyć niebezpieczną kolekcję. Jeśli jednak wkleimy ten kod do IDE, otrzymamy ostrzeżenie: „Niezaznaczone wywołanie add(E) as a member of raw type of java.util.List” Powiedziano nam, że coś może pójść nie tak podczas dodawania elementu do kolekcji, która nie ma typu ogólnego. Ale co oznacza wyrażenie „surowy typ”? Typ surowy to klasa ogólna, której typ został usunięty. Innymi słowy, List myList1 jest typem surowym . Przeciwieństwem typu surowego jest typ generyczny — klasa generyczna ze wskazaniem typów sparametryzowanych . Na przykład Lista<String> mojaLista1. Możesz zapytać, dlaczego język pozwala na użycie surowych typów ? Powód jest prosty. Twórcy Javy pozostawili w języku obsługę surowych typów , aby uniknąć problemów ze zgodnością. Do czasu wydania Javy 5.0 (w tej wersji po raz pierwszy pojawiły się typy generyczne), wiele kodu zostało już napisanych przy użyciu surowych typów . W rezultacie mechanizm ten jest nadal obsługiwany. Wielokrotnie wspominaliśmy na lekcjach klasyczną książkę Joshuy Blocha „Efektywna Java”. Jako jeden z twórców języka nie pominął w swojej książce typów surowych i ogólnych .Czym są generyczne w Javie?  - 2Rozdział 23 książki ma bardzo wymowny tytuł: „Nie używaj surowych typów w nowym kodzie”. O tym musisz pamiętać. Używając klas ogólnych, nigdy nie zmieniaj typu ogólnego w typ surowy .

Metody ogólne

Java pozwala na parametryzację poszczególnych metod poprzez tworzenie tak zwanych metod generycznych. W czym pomagają takie metody? Przede wszystkim są one pomocne w tym, że pozwalają pracować z różnymi typami parametrów metod. Jeśli tę samą logikę można bezpiecznie zastosować do różnych typów, metoda generyczna może być doskonałym rozwiązaniem. Rozważmy to jako bardzo prosty przykład: Załóżmy, że mamy listę o nazwie myList1 . Chcemy usunąć wszystkie wartości z listy i wypełnić wszystkie puste miejsca nowymi wartościami. Oto jak 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("Old String 1");
       strings.add("Old String 2");
       strings.add("Old String 3");

       fill(strings, "New String");

       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)
Piszemy <T> przed zwracanym typem. Oznacza to, że mamy do czynienia z metodą ogólną. W tym przypadku metoda przyjmuje jako dane wejściowe 2 parametry: listę obiektów T i inny oddzielny obiekt T. Używając <T>, parametryzujemy typy parametrów metody: nie możemy przekazać listy ciągów ani liczby całkowitej. Lista łańcuchów i łańcuchów, lista liczb całkowitych i liczba całkowita, lista naszych własnych obiektów Cat i inny obiekt Cat — oto, co musimy zrobić. Metoda main() ilustruje, w jaki sposób metoda fill() może być łatwo wykorzystana do pracy z różnymi typami danych. Najpierw używamy metody z listą ciągów i ciągiem jako danymi wejściowymi, a następnie z listą liczb całkowitych i liczbą całkowitą. Wyjście konsoli:
[New String, New String, New String] [888, 888, 888]
Wyobraź sobie, że nie mamy metod ogólnych i potrzebujemy logiki metody fill() dla 30 różnych klas. Musielibyśmy napisać tę samą metodę 30 razy dla różnych typów danych! Ale dzięki metodom generycznym możemy ponownie wykorzystać nasz kod! :)

Klasy ogólne

Nie jesteś ograniczony do ogólnych klas dostępnych w standardowych bibliotekach Java — możesz 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("Old String");
       System.out.println(stringBox.get());
       stringBox.set("New String");

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

       stringBox.set(12345); // Compilation error!
   }
}
Nasza klasa Box<T> jest klasą ogólną. Gdy przypiszemy typ danych ( <T> ) podczas tworzenia, nie będziemy już mogli umieszczać w nim obiektów innych typów. Można to zobaczyć na przykładzie. Tworząc nasz obiekt zaznaczyliśmy, że będzie on działał z Stringami:
Box<String> stringBox = new Box<>();
A w ostatniej linijce kodu, gdy próbujemy umieścić w ramce liczbę 12345, otrzymujemy błąd kompilacji! To jest takie proste! Stworzyliśmy naszą własną klasę ogólną! :) Na tym dzisiejsza lekcja dobiega końca. Ale nie żegnamy się z lekami generycznymi! Na następnych lekcjach omówimy bardziej zaawansowane funkcje, więc nie odchodź! ) Aby utrwalić to, czego się nauczyłeś, zalecamy obejrzenie lekcji wideo z naszego kursu języka Java
Życzymy sukcesów w nauce! :)
Komentarze
  • Popularne
  • Najnowsze
  • Najstarsze
Musisz się zalogować, aby dodać komentarz
Ta strona nie ma jeszcze żadnych komentarzy