Witaj Amigo!

- Cześć, Ellie!

— Dzisiaj Risha i ja opowiemy wam wszystko o lekach generycznych.

„Więc w pewnym sensie wiem już prawie wszystko.

Prawie wszystkie, ale nie wszystkie.

- Tak? Dobra, jestem gotowy do słuchania.

– W takim razie zaczynajmy.

Typy generyczne w Javie to klasy zawierające typy parametrów.

Powody pojawienia się generycznych - patrz komentarz w kodzie:

Przykład
ArrayList stringList = new ArrayList();
stringList.add("abc"); //добавляем строку в список
stringList.add("abc"); //добавляем строку в список
stringList.add( 1 ); //добавляем число в список

for(Object o: stringList)
{
 String s = (String) o; //тут будет исключение, когда дойдем до element-числа
}

Jak leki generyczne rozwiązują problem:

Przykład
ArrayList<String> stringList = new ArrayList<String>();
stringList.add("abc"); //добавляем строку в список
stringList.add("abc"); //добавляем строку в список
stringList.add( 1 ); //тут будет ошибка компиляции

for(Object o: stringList)
{
 String s = (String) o;
}

Taki kod po prostu się nie skompiluje, a błąd z dodaniem danych złego typu zostanie zauważony na etapie kompilacji.

"Już to wiem.

- To wspaniale. Powtarzanie jest matką nauki.

Ale programiści Java trochę pomieszali podczas tworzenia Generic i zamiast tworzyć pełnoprawne typy z parametrami, schrzanili sprytną optymalizację. W rzeczywistości do typów nie dodano żadnych informacji o ich typach parametrów, a cała magia wydarzyła się na etapie kompilacji.

Kod z generykami
List<String> strings = new ArrayList<String>();
strings.add("abc");
strings.add("abc");
strings.add( 1); // тут ошибка компиляции

for(String s: strings)
{
 System.out.println(s);
}
Co naprawdę się dzieje
List strings = new ArrayList();

strings.add((String)"abc");
strings.add((String)"abc");
strings.add((String) 1); //ошибка компиляции

for(String s: strings)
{
 System.out.println(s);
}

- Hitro, tak.

Tak, ale takie podejście ma efekt uboczny. Wewnątrz klasy ogólnej nie są przechowywane żadne informacje o typie jej parametru. To podejście zostało później nazwane wymazywaniem czcionek.

Te. jeśli masz własną klasę z typami parametrów, nie możesz używać informacji o nich wewnątrz klasy.

Kod z generykami
class Zoo<T>
{
 ArrayList<T> pets = new ArrayList<T>();

 public T createAnimal()
 {
  T animal = new T();
  pets.add(animal)
  return animal;
 }
}
Co naprawdę się dzieje
class Zoo
{
 ArrayList pets = new ArrayList();

 public Object createAnimal()
 {
  Object animal = new ???();
  pets.add(animal)
  return animal;
 }
}

Podczas kompilacji wszystkie typy parametrów są zastępowane przez Object. I nie ma informacji o typie, który został mu przekazany w klasie.

Tak, zgadzam się, to nie jest zbyt dobre.

- Dobra, to nie takie straszne, później ci opowiem, jak nauczyli się obejść ten problem.

Ale to nie wszystko. Java umożliwia określenie typu nadrzędnego dla typów parametrów. W tym celu używane jest słowo kluczowe extends. Przykład:

Kod z generykami
class Zoo<T extends Cat>
{
 T cat;

 T getCat()
 {
  return cat;
 }

 void setCat (T cat)
 {
  this.cat = cat;
 }

 String getCatName()
 {
  return this.cat.getName();
 }
}
Co naprawdę się dzieje
class Zoo
{
 Cat cat;

 Cat getCat()
 {
  return cat;
 }

 void setCat(Cat cat)
 {
  this.cat = cat;
 }

 String getCatName()
 {
  return this.cat.getName();
 }
}

Zwróć uwagę na dwa fakty.

Po pierwsze, możliwe jest teraz przekazanie nie dowolnego typu jako typu parametru, ale typu Cat lub jednego z jego potomków.

Po drugie, w klasie Zoo zmienne typu T mogą teraz wywoływać metody klasy Cat. Dlaczego - wyjaśniono w kolumnie po prawej stronie (ponieważ typ T zostanie wszędzie zastąpiony typem Cat)

- Tak. Jeśli powiedzieliśmy, że typ Cat lub jego potomkowie są przekazywani jako parametr typu, to jesteśmy pewni, że typ T musi mieć metody klasy Cat.

Dobrze. Sprytny.