Samouczek Java Generics - czym są typy generyczne i jak z nich korzystać?

Java Generics to jedna z najważniejszych cech języka Java. Idea stojąca za rodzajami generycznymi jest dość prosta, jednak czasami wydaje się skomplikowana z powodu odejścia od zwykłej związanej z nią składni.

Celem tego samouczka jest wprowadzenie w łatwą do zrozumienia użyteczną koncepcję leków generycznych.

Zanim jednak zagłębimy się w same typy ogólne, zastanówmy się, dlaczego w ogóle były potrzebne typy generyczne w języku Java.




Cel Java Generics

Przed wprowadzeniem typów ogólnych w Javie 5 można było napisać i skompilować taki fragment kodu bez zgłaszania błędu lub ostrzeżenia:

List list = new ArrayList(); list.add('hey'); list.add(new Object());

Możesz dodać wartości dowolnego typu do listy lub innej kolekcji Java bez konieczności deklarowania typu przechowywanych danych. Ale kiedy pobierasz wartości z listy, musisz jawnie rzutować je na określony typ.


Rozważ powtórzenie powyższej listy.

for (int i=0; i< list.size(); i++) {
String value = (String) list.get(i); //CastClassException when i=1 }

Zezwolenie na utworzenie listy bez uprzedniego zadeklarowania typu przechowywanych danych, tak jak to zrobiliśmy, może spowodować, że programiści będą popełniać błędy, takie jak powyżej, które rzuca ClassCastExceptions w trakcie działania.

Wprowadzono generyki, aby zapobiec popełnianiu takich błędów przez programistów.

W przypadku typów ogólnych można jawnie zadeklarować typ danych, które będą przechowywane podczas tworzenia kolekcji Java, jak pokazano w poniższym przykładzie.


Uwaga:Nadal można utworzyć obiekt kolekcji Java bez określania typu przechowywanych danych, ale nie jest to zalecane. List stringList = new ArrayList();

Teraz nie można omyłkowo zapisać liczby całkowitej na liście typów ciągów bez zgłaszania błędu w czasie kompilacji. Gwarantuje to, że Twój program nie napotka błędów w czasie wykonywania.

stringList.add(new Integer(4)); //Compile time Error

Głównym celem wprowadzenia typów generycznych do Javy było uniknięcie wpadnięcia na ClassCastExceptions podczas działania.



Tworzenie generycznych Java

Możesz używać typów ogólnych do tworzenia klas i metod Java. Przyjrzyjmy się przykładom tworzenia typów ogólnych każdego typu.

Klasa ogólna

Podczas tworzenia klasy ogólnej parametr typu dla klasy jest dodawany na końcu nazwy klasy w obrębie kąta nawiasy.


public class GenericClass {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return this.item;
} }

Tutaj, T jest parametrem typu danych. T, N i E to niektóre z liter używanych w parametrach typu danych zgodnie z konwencjami języka Java.

W powyższym przykładzie możesz przekazać mu określony typ danych podczas tworzenia obiektu GenericClass.

public static void main(String[] args) {
GenericClass gc1 = new GenericClass();
gc1.setItem('hello');
String item1 = gc1.getItem(); // 'hello'
gc1.setItem(new Object()); //Error
GenericClass gc2 = new GenericClass();
gc2.setItem(new Integer(1));
Integer item2 = gc2.getItem(); // 1
gc2.setItem('hello'); //Error }

Nie można przekazać pierwotnego typu danych do parametru typu danych podczas tworzenia obiektu klasy ogólnej. Jako parametry typu można przekazywać tylko typy danych, które rozszerzają typ Object.

Na przykład:


GenericClass gc3 = new GenericClass(); //Error

Metody ogólne

Tworzenie metod ogólnych przebiega według podobnego wzorca do tworzenia klas ogólnych. Możesz zaimplementować metodę ogólną w klasie ogólnej, a także inną niż ogólna.

public class GenericMethodClass {
public static void printItems(T[] arr){
for (int i=0; i< arr.length; i++) {

System.out.println(arr[i]);
}
}
public static void main(String[] args) {
String[] arr1 = {'Cat', 'Dog', 'Mouse'};
Integer[] arr2 = {1, 2, 3};

GenericMethodClass.printItems(arr1); // 'Cat', 'Dog', 'Mouse'
GenericMethodClass.printItems(arr2); // 1, 2, 3
} }

Tutaj możesz przekazać tablicę określonego typu, aby sparametryzować metodę. Metoda ogólna PrintItems() wykonuje iterację przez przekazaną tablicę i wyświetla elementy przechowywane tak, jak zwykła metoda Java.



Ograniczone parametry typu

Jak dotąd generyczne klasy i metody, które utworzyliśmy powyżej, można sparametryzować na dowolny typ danych inny niż typy pierwotne. Ale co by było, gdybyśmy chcieli ograniczyć typy danych, które można przekazywać do typów ogólnych? W tym miejscu pojawiają się ograniczone parametry typu.

Można powiązać typy danych akceptowane przez klasę ogólną lub metodę, określając, że powinna to być podklasa innego typu danych.


Na przykład:

//accepts only subclasses of List public class UpperBoundedClass{
//accepts only subclasses of List
public void UpperBoundedMethod(T[] arr) {
} }

Tutaj UpperBoundedClass i UpperBoundedMethod można sparametryzować tylko przy użyciu podtypów List typ danych.

List typ danych działa jako górna granica parametru typu. Jeśli spróbujesz użyć typu danych, który nie jest podtypem List, zgłosi błąd w czasie kompilacji.

Granice nie ograniczają się tylko do zajęć. Możesz również przepuszczać interfejsy. Rozszerzenie interfejsu oznacza w tym przypadku zaimplementowanie interfejsu.

Parametr może również mieć wiele granic, jak pokazano w tym przykładzie.

//accepts only subclasses of both Mammal and Animal public class MultipleBoundedClass{
//accepts only subclasses of both Mammal and Animal
public void MultipleBoundedMethod(T[] arr){
} }

Akceptujący typ danych musi być podklasą zarówno klas Animal, jak i Mammal. Jeśli jedna z tych granic jest klasą, musi znajdować się na pierwszym miejscu w deklaracji powiązania.

W powyższym przykładzie, jeśli Mammal jest klasą, a Animal jest interfejsem, Mammal musi być pierwszy, jak pokazano powyżej. W przeciwnym razie kod zgłasza błąd w czasie kompilacji.



Symbole wieloznaczne Java Generics

Symbole wieloznaczne służą do przekazywania parametrów typów ogólnych do metod. W przeciwieństwie do metody ogólnej, tutaj parametr ogólny jest przekazywany do parametrów akceptowanych przez metodę, które różnią się od parametru typu danych, który omówiliśmy powyżej. Symbol wieloznaczny jest reprezentowany przez? symbol.

public void printItems(List list) {
for (int i=0; i< list.size(); i++) {
System.out.println(list.get(i));
} }

Powyższe printItems() metoda przyjmuje jako parametr listy dowolnego typu danych. Dzięki temu programiści nie muszą powtarzać kodów dla list różnych typów danych, co miałoby miejsce bez typów ogólnych.

Górne ograniczone symbole wieloznaczne

Jeśli chcemy ograniczyć typy danych przechowywane na liście akceptowanej przez metodę, możemy użyć ograniczonych symboli wieloznacznych.

Przykład:

public void printSubTypes(List list) {
for (int i=0; i< list.size(); i++) {
System.out.println(list.get(i));
} }

printSubTypes() akceptuje tylko listy, które przechowują podtypy Color. Akceptuje listę obiektów RedColor lub BlueColor, ale nie akceptuje listy obiektów Animal. Dzieje się tak, ponieważ Animal nie jest podtypem Color. To jest przykład symbolu wieloznacznego ograniczonego górną granicą.

Dolne ograniczone symbole wieloznaczne

Podobnie, gdybyśmy mieli:

public void printSuperTypes(List list) {
for (int i=0; i< list.size(); i++) {
System.out.println(list.get(i));
} }

wtedy printSuperTypes() akceptuje tylko listy, które przechowują super typy klasy Dog. Akceptuje listę obiektów typu Mammal lub Animal, ale nie listę obiektów LabDog, ponieważ LabDog nie jest nadklasą Dog, ale podklasą. To jest przykład symbolu wieloznacznego z dolną granicą.



Wniosek

Java Generics stała się funkcją, bez której programiści nie mogą żyć od czasu jej wprowadzenia.

Ta popularność wynika z jej wpływu na ułatwienie życia programistom. Oprócz zapobiegania popełnianiu błędów w kodowaniu, użycie typów ogólnych sprawia, że ​​kod jest mniej powtarzalny. Czy zauważyłeś, jak uogólnia klasy i metody, aby uniknąć konieczności powtarzania kodu dla różnych typów danych?

Aby stać się ekspertem w tym języku, ważne jest, aby dobrze znać rodzaje generyczne. Tak więc, zastosowanie tego, czego nauczyłeś się w tym samouczku, w praktycznym kodzie, jest sposobem na teraz.