Java Generics - szybki przewodnik
Byłoby miło, gdybyśmy mogli napisać pojedynczą metodę sortowania, która mogłaby sortować elementy w tablicy Integer, tablicy String lub tablicy dowolnego typu, która obsługuje porządkowanie.
Metody ogólne i klasy generyczne języka Java umożliwiają programistom określanie, odpowiednio, za pomocą pojedynczej deklaracji metody zestawu powiązanych metod lub za pomocą pojedynczej deklaracji klasy zestawu powiązanych typów.
Typy generyczne zapewniają również bezpieczeństwo typu w czasie kompilacji, które umożliwia programistom wyłapywanie nieprawidłowych typów w czasie kompilacji.
Korzystając z koncepcji Java Generic, możemy napisać ogólną metodę sortowania tablicy obiektów, a następnie wywołać metodę ogólną z tablicami Integer, tablicami podwójnymi, tablicami String i tak dalej, aby posortować elementy tablicy.
Konfiguracja środowiska lokalnego
JUnit to framework dla Javy, więc pierwszym wymaganiem jest zainstalowanie JDK na twoim komputerze.
Wymagania systemowe
JDK | 1.5 lub nowszy. |
---|---|
Pamięć | Brak minimalnych wymagań. |
Miejsca na dysku | Brak minimalnych wymagań. |
System operacyjny | Brak minimalnych wymagań. |
Krok 1: Zweryfikuj instalację Java na swoim komputerze
Przede wszystkim otwórz konsolę i wykonaj polecenie java w oparciu o system operacyjny, na którym pracujesz.
OS | Zadanie | Komenda |
---|---|---|
Windows | Otwórz konsolę poleceń | c: \> java -version |
Linux | Otwórz terminal poleceń | $ java -version |
Prochowiec | Otwórz terminal | maszyna: <joseph $ java -version |
Sprawdźmy dane wyjściowe dla wszystkich systemów operacyjnych -
OS | Wynik |
---|---|
Windows | wersja java „1.6.0_21” Java (TM) SE Runtime Environment (kompilacja 1.6.0_21-b07) Maszyna wirtualna klienta Java HotSpot (TM) (kompilacja 17.0-b17, tryb mieszany, udostępnianie) |
Linux | wersja java „1.6.0_21” Java (TM) SE Runtime Environment (kompilacja 1.6.0_21-b07) Maszyna wirtualna klienta Java HotSpot (TM) (kompilacja 17.0-b17, tryb mieszany, udostępnianie) |
Prochowiec | wersja java „1.6.0_21” Java (TM) SE Runtime Environment (kompilacja 1.6.0_21-b07) Java HotSpot (TM) 64-bitowa maszyna wirtualna serwera (kompilacja 17.0-b17, tryb mieszany, udostępnianie) |
Jeśli nie masz zainstalowanej Java w swoim systemie, pobierz pakiet Java Software Development Kit (SDK) z poniższego łącza https://www.oracle.com. Zakładamy Java 1.6.0_21 jako zainstalowaną wersję dla tego samouczka.
Krok 2: Ustaw środowisko JAVA
Ustaw JAVA_HOMEzmienna środowiskowa wskazująca lokalizację katalogu podstawowego, w którym na komputerze jest zainstalowana Java. Na przykład.
OS | Wynik |
---|---|
Windows | Ustaw zmienną środowiskową JAVA_HOME na C: \ Program Files \ Java \ jdk1.6.0_21 |
Linux | eksportuj JAVA_HOME = / usr / local / java-current |
Prochowiec | eksportuj JAVA_HOME = / Library / Java / Home |
Dołącz lokalizację kompilatora Java do ścieżki systemowej.
OS | Wynik |
---|---|
Windows | Dołącz ciąg C:\Program Files\Java\jdk1.6.0_21\bin na końcu zmiennej systemowej, Path. |
Linux | eksportuj PATH = $PATH:$JAVA_HOME / bin / |
Prochowiec | nie wymagane |
Sprawdź instalację oprogramowania Java za pomocą polecenia java -version jak wyjaśniono powyżej.
Deklaracja klasy ogólnej wygląda jak deklaracja klasy nieogólnej, z tą różnicą, że po nazwie klasy następuje sekcja parametru typu.
Sekcja parametru typu klasy ogólnej może mieć jeden lub więcej parametrów typu oddzielonych przecinkami. Te klasy są znane jako sparametryzowane klasy lub sparametryzowane typy, ponieważ akceptują jeden lub więcej parametrów.
Składnia
public class Box<T> {
private T t;
}
Gdzie
Box - Box to klasa ogólna.
T- parametr typu ogólnego przekazany do klasy ogólnej. Może zająć dowolny obiekt.
t - Wystąpienie typu ogólnego T.
Opis
T jest parametrem typu przekazywanym do klasy ogólnej Box i powinien być przekazywany podczas tworzenia obiektu Box.
Przykład
Utwórz następujący program Java za pomocą dowolnego wybranego edytora.
GenericsTester.java
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
integerBox.add(new Integer(10));
stringBox.add(new String("Hello World"));
System.out.printf("Integer Value :%d\n", integerBox.get());
System.out.printf("String Value :%s\n", stringBox.get());
}
}
class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
Spowoduje to następujący wynik.
Wynik
Integer Value :10
String Value :Hello World
Zgodnie z konwencją nazwy parametrów typu są nazywane pojedynczymi dużymi literami, dzięki czemu parametr typu można łatwo rozróżnić za pomocą zwykłej nazwy klasy lub interfejsu. Poniżej znajduje się lista najczęściej używanych nazw parametrów typu -
E - Element i jest używany głównie przez środowisko Java Collections.
K - Klucz i jest używany głównie do reprezentowania typu parametru klucza mapy.
V - Wartość i jest używana głównie do reprezentowania typu parametru wartości mapy.
N - Liczba i służy głównie do reprezentowania liczb.
T - Typ i jest używany głównie do reprezentowania pierwszego parametru typu ogólnego.
S - Typ i jest używany głównie do reprezentowania drugiego parametru typu ogólnego.
U - Typ i jest używany głównie do reprezentowania trzeciego parametru typu ogólnego.
V - Typ i jest używany głównie do reprezentowania czwartego parametru typu ogólnego.
Poniższy przykład pokaże powyższą koncepcję.
Przykład
Utwórz następujący program Java za pomocą dowolnego wybranego edytora.
GenericsTester.java
package com.tutorialspoint;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer, String> box = new Box<Integer, String>();
box.add(Integer.valueOf(10),"Hello World");
System.out.printf("Integer Value :%d\n", box.getFirst());
System.out.printf("String Value :%s\n", box.getSecond());
Pair<String, Integer> pair = new Pair<String, Integer>();
pair.addKeyValue("1", Integer.valueOf(10));
System.out.printf("(Pair)Integer Value :%d\n", pair.getValue("1"));
CustomList<Box> list = new CustomList<Box>();
list.addItem(box);
System.out.printf("(CustomList)Integer Value :%d\n", list.getItem(0).getFirst());
}
}
class Box<T, S> {
private T t;
private S s;
public void add(T t, S s) {
this.t = t;
this.s = s;
}
public T getFirst() {
return t;
}
public S getSecond() {
return s;
}
}
class Pair<K,V>{
private Map<K,V> map = new HashMap<K,V>();
public void addKeyValue(K key, V value) {
map.put(key, value);
}
public V getValue(K key) {
return map.get(key);
}
}
class CustomList<E>{
private List<E> list = new ArrayList<E>();
public void addItem(E value) {
list.add(value);
}
public E getItem(int index) {
return list.get(index);
}
}
Spowoduje to następujący wynik.
Wynik
Integer Value :10
String Value :Hello World
(Pair)Integer Value :10
(CustomList)Integer Value :10
Wnioskowanie o typie reprezentuje zdolność kompilatora Java do spojrzenia na wywołanie metody i odpowiadającą jej deklarację w celu sprawdzenia i określenia argumentów typu. Algorytm wnioskowania sprawdza typy argumentów i, jeśli jest dostępny, zwracany jest przypisany typ. Algorytmy wnioskowania próbują znaleźć określony typ, który może spełnić wszystkie parametry typu.
Kompilator generuje niesprawdzone ostrzeżenie o konwersji w przypadku, gdy wnioskowanie o typie nie jest używane.
Składnia
Box<Integer> integerBox = new Box<>();
Gdzie
Box - Box to klasa ogólna.
<> - Operator rombu oznacza wnioskowanie o typie.
Opis
Używając operatora diamentu, kompilator określa typ parametru. Ten operator jest dostępny od wersji Java SE 7 i nowszych.
Przykład
Utwórz następujący program Java za pomocą dowolnego wybranego edytora.
GenericsTester.java
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
//type inference
Box<Integer> integerBox = new Box<>();
//unchecked conversion warning
Box<String> stringBox = new Box<String>();
integerBox.add(new Integer(10));
stringBox.add(new String("Hello World"));
System.out.printf("Integer Value :%d\n", integerBox.get());
System.out.printf("String Value :%s\n", stringBox.get());
}
}
class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
Spowoduje to następujący wynik.
Wynik
Integer Value :10
String Value :Hello World
Możesz napisać jedną ogólną deklarację metody, którą można wywołać z argumentami różnych typów. Na podstawie typów argumentów przekazanych do metody ogólnej kompilator odpowiednio obsługuje każde wywołanie metody. Poniżej przedstawiono zasady definiowania metod ogólnych -
Wszystkie deklaracje metod ogólnych mają sekcję parametru typu oddzieloną nawiasami ostrymi (<i>), która poprzedza zwracany typ metody (<E> w następnym przykładzie).
Każda sekcja parametru typu zawiera jeden lub więcej parametrów typu oddzielonych przecinkami. Parametr typu, znany również jako zmienna typu, jest identyfikatorem określającym nazwę typu ogólnego.
Parametry typu mogą służyć do deklarowania zwracanego typu i pełnić rolę symboli zastępczych dla typów argumentów przekazanych do metody ogólnej, które są znane jako argumenty typu rzeczywistego.
Treść metody ogólnej jest deklarowana jak każda inna metoda. Należy zauważyć, że parametry typu mogą reprezentować tylko typy referencyjne, a nie typy pierwotne (takie jak int, double i char).
Przykład
Poniższy przykład ilustruje, jak możemy wydrukować tablicę innego typu przy użyciu jednej metody Generic -
public class GenericMethodTest {
// generic method printArray
public static < E > void printArray( E[] inputArray ) {
// Display array elements
for(E element : inputArray) {
System.out.printf("%s ", element);
}
System.out.println();
}
public static void main(String args[]) {
// Create arrays of Integer, Double and Character
Integer[] intArray = { 1, 2, 3, 4, 5 };
Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
System.out.println("Array integerArray contains:");
printArray(intArray); // pass an Integer array
System.out.println("\nArray doubleArray contains:");
printArray(doubleArray); // pass a Double array
System.out.println("\nArray characterArray contains:");
printArray(charArray); // pass a Character array
}
}
To da następujący wynik -
Wynik
Array integerArray contains:
1 2 3 4 5
Array doubleArray contains:
1.1 2.2 3.3 4.4
Array characterArray contains:
H E L L O
Klasa ogólna może mieć wiele parametrów typu. Poniższy przykład pokaże powyższą koncepcję.
Przykład
Utwórz następujący program Java za pomocą dowolnego wybranego edytora.
GenericsTester.java
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer, String> box = new Box<Integer, String>();
box.add(Integer.valueOf(10),"Hello World");
System.out.printf("Integer Value :%d\n", box.getFirst());
System.out.printf("String Value :%s\n", box.getSecond());
Box<String, String> box1 = new Box<String, String>();
box1.add("Message","Hello World");
System.out.printf("String Value :%s\n", box1.getFirst());
System.out.printf("String Value :%s\n", box1.getSecond());
}
}
class Box<T, S> {
private T t;
private S s;
public void add(T t, S s) {
this.t = t;
this.s = s;
}
public T getFirst() {
return t;
}
public S getSecond() {
return s;
}
}
Spowoduje to następujący wynik.
Wynik
Integer Value :10
String Value :Hello World
String Value :Message
String Value :Hello World
Klasa ogólna może mieć sparametryzowane typy, w których parametr typu można zastąpić sparametryzowanym typem. Poniższy przykład pokaże powyższą koncepcję.
Przykład
Utwórz następujący program Java za pomocą dowolnego wybranego edytora.
GenericsTester.java
package com.tutorialspoint;
import java.util.ArrayList;
import java.util.List;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer, List<String>> box
= new Box<Integer, List<String>>();
List<String> messages = new ArrayList<String>();
messages.add("Hi");
messages.add("Hello");
messages.add("Bye");
box.add(Integer.valueOf(10),messages);
System.out.printf("Integer Value :%d\n", box.getFirst());
System.out.printf("String Value :%s\n", box.getSecond());
}
}
class Box<T, S> {
private T t;
private S s;
public void add(T t, S s) {
this.t = t;
this.s = s;
}
public T getFirst() {
return t;
}
public S getSecond() {
return s;
}
}
Spowoduje to następujący wynik.
Wynik
Integer Value :10
String Value :[Hi, Hello, Bye]
Surowy typ jest obiektem klasy ogólnej lub interfejsu, jeśli jego argumenty typu nie są przekazywane podczas jego tworzenia. Poniższy przykład pokaże powyższą koncepcję.
Przykład
Utwórz następujący program Java za pomocą dowolnego wybranego edytora.
GenericsTester.java
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer> box = new Box<Integer>();
box.set(Integer.valueOf(10));
System.out.printf("Integer Value :%d\n", box.getData());
Box rawBox = new Box();
//No warning
rawBox = box;
System.out.printf("Integer Value :%d\n", rawBox.getData());
//Warning for unchecked invocation to set(T)
rawBox.set(Integer.valueOf(10));
System.out.printf("Integer Value :%d\n", rawBox.getData());
//Warning for unchecked conversion
box = rawBox;
System.out.printf("Integer Value :%d\n", box.getData());
}
}
class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T getData() {
return t;
}
}
Spowoduje to następujący wynik.
Wynik
Integer Value :10
Integer Value :10
Integer Value :10
Integer Value :10
Może się zdarzyć, że będziesz chciał ograniczyć rodzaje typów, które mogą być przekazywane do parametru typu. Na przykład metoda, która operuje na liczbach, może chcieć akceptować tylko wystąpienia Number lub jej podklasy. Do tego służą parametry typu ograniczonego.
Aby zadeklarować parametr typu ograniczonego, należy podać nazwę parametru typu, po którym następuje słowo kluczowe extends, a następnie jego górna granica.
Przykład
Poniższy przykład ilustruje, w jaki sposób extends jest używane w ogólnym sensie w znaczeniu „rozszerza” (jak w klasach) lub „implementuje” (jak w interfejsach). Ten przykład jest metodą Generic zwracającą największy z trzech porównywalnych obiektów -
public class MaximumTest {
// determines the largest of three Comparable objects
public static <T extends Comparable<T>> T maximum(T x, T y, T z) {
T max = x; // assume x is initially the largest
if(y.compareTo(max) > 0) {
max = y; // y is the largest so far
}
if(z.compareTo(max) > 0) {
max = z; // z is the largest now
}
return max; // returns the largest object
}
public static void main(String args[]) {
System.out.printf("Max of %d, %d and %d is %d\n\n",
3, 4, 5, maximum( 3, 4, 5 ));
System.out.printf("Max of %.1f,%.1f and %.1f is %.1f\n\n",
6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ));
System.out.printf("Max of %s, %s and %s is %s\n","pear",
"apple", "orange", maximum("pear", "apple", "orange"));
}
}
To da następujący wynik -
Wynik
Max of 3, 4 and 5 is 5
Max of 6.6,8.8 and 7.7 is 8.8
Max of pear, apple and orange is pear
Parametr typu może mieć wiele granic.
Składnia
public static <T extends Number & Comparable<T>> T maximum(T x, T y, T z)
Gdzie
maximum - maksimum to metoda ogólna.
T- parametr typu ogólnego przekazany do metody ogólnej. Może zająć dowolny obiekt.
Opis
T jest parametrem typu przekazywanym do klasy ogólnej Box i powinien być podtypem klasy Number i musi zawierać interfejs Comparable. W przypadku, gdy klasa jest przekazywana jako związana, powinna zostać przekazana jako pierwsza przed interfejsem, w przeciwnym razie wystąpi błąd czasu kompilacji.
Przykład
Utwórz następujący program Java za pomocą dowolnego wybranego edytora.
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
System.out.printf("Max of %d, %d and %d is %d\n\n",
3, 4, 5, maximum( 3, 4, 5 ));
System.out.printf("Max of %.1f,%.1f and %.1f is %.1f\n\n",
6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ));
}
public static <T extends Number
& Comparable<T>> T maximum(T x, T y, T z) {
T max = x;
if(y.compareTo(max) > 0) {
max = y;
}
if(z.compareTo(max) > 0) {
max = z;
}
return max;
}
// Compiler throws error in case of below declaration
/* public static <T extends Comparable<T>
& Number> T maximum1(T x, T y, T z) {
T max = x;
if(y.compareTo(max) > 0) {
max = y;
}
if(z.compareTo(max) > 0) {
max = z;
}
return max;
}*/
}
To da następujący wynik -
Wynik
Max of 3, 4 and 5 is 5
Max of 6.6,8.8 and 7.7 is 8.8
Java zapewnia ogólną obsługę interfejsu List.
Składnia
List<T> list = new ArrayList<T>();
Gdzie
list - obiekt interfejsu List.
T - parametr typu ogólnego przekazany podczas deklaracji listy.
Opis
T jest parametrem typu przekazywanym do ogólnego interfejsu List i jego klasy implementacyjnej ArrayList.
Przykład
Utwórz następujący program Java za pomocą dowolnego wybranego edytora.
package com.tutorialspoint;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class GenericsTester {
public static void main(String[] args) {
List<Integer> integerList = new ArrayList<Integer>();
integerList.add(Integer.valueOf(10));
integerList.add(Integer.valueOf(11));
List<String> stringList = new ArrayList<String>();
stringList.add("Hello World");
stringList.add("Hi World");
System.out.printf("Integer Value :%d\n", integerList.get(0));
System.out.printf("String Value :%s\n", stringList.get(0));
for(Integer data: integerList) {
System.out.printf("Integer Value :%d\n", data);
}
Iterator<String> stringIterator = stringList.iterator();
while(stringIterator.hasNext()) {
System.out.printf("String Value :%s\n", stringIterator.next());
}
}
}
To da następujący wynik -
Wynik
Integer Value :10
String Value :Hello World
Integer Value :10
Integer Value :11
String Value :Hello World
String Value :Hi World
Java zapewnia ogólne wsparcie w interfejsie Set.
Składnia
Set<T> set = new HashSet<T>();
Gdzie
set - obiekt Set Interface.
T - parametr typu ogólnego przekazany podczas deklaracji zestawu.
Opis
T jest parametrem typu przekazywanym do zestawu ogólnego interfejsu i jego klasy implementacyjnej HashSet.
Przykład
Utwórz następujący program Java za pomocą dowolnego wybranego edytora.
package com.tutorialspoint;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class GenericsTester {
public static void main(String[] args) {
Set<Integer> integerSet = new HashSet<Integer>();
integerSet.add(Integer.valueOf(10));
integerSet.add(Integer.valueOf(11));
Set<String> stringSet = new HashSet<String>();
stringSet.add("Hello World");
stringSet.add("Hi World");
for(Integer data: integerSet) {
System.out.printf("Integer Value :%d\n", data);
}
Iterator<String> stringIterator = stringSet.iterator();
while(stringIterator.hasNext()) {
System.out.printf("String Value :%s\n", stringIterator.next());
}
}
}
To da następujący wynik -
Wynik
Integer Value :10
Integer Value :11
String Value :Hello World
String Value :Hi World
Java zapewnia ogólne wsparcie w interfejsie Map.
Składnia
Set<T> set = new HashSet<T>();
Gdzie
set - obiekt Set Interface.
T - parametr typu ogólnego przekazany podczas deklaracji zestawu.
Opis
T jest parametrem typu przekazywanym do zestawu ogólnego interfejsu i jego klasy implementacyjnej HashSet.
Przykład
Utwórz następujący program Java za pomocą dowolnego wybranego edytora.
package com.tutorialspoint;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class GenericsTester {
public static void main(String[] args) {
Map<Integer,Integer> integerMap
= new HashMap<Integer,Integer>();
integerMap.put(1, 10);
integerMap.put(2, 11);
Map<String,String> stringMap = new HashMap<String,String>();
stringMap.put("1", "Hello World");
stringMap.put("2","Hi World");
System.out.printf("Integer Value :%d\n", integerMap.get(1));
System.out.printf("String Value :%s\n", stringMap.get("1"));
// iterate keys.
Iterator<Integer> integerIterator = integerMap.keySet().iterator();
while(integerIterator.hasNext()) {
System.out.printf("Integer Value :%d\n", integerIterator.next());
}
// iterate values.
Iterator<String> stringIterator = stringMap.values().iterator();
while(stringIterator.hasNext()) {
System.out.printf("String Value :%s\n", stringIterator.next());
}
}
}
To da następujący wynik -
Wynik
Integer Value :10
String Value :Hello World
Integer Value :1
Integer Value :2
String Value :Hello World
String Value :Hi World
Znak zapytania (?) Reprezentuje symbol wieloznaczny, oznacza nieznany typ w rodzajach generycznych. Może się zdarzyć, że będziesz chciał ograniczyć rodzaje typów, które mogą być przekazywane do parametru typu. Na przykład metoda, która operuje na liczbach, może chcieć akceptować tylko wystąpienia Number lub jej podklasy.
Aby zadeklarować parametr wieloznaczny ograniczający górną granicę, należy podać znak?, Po którym następuje słowo kluczowe extends, po którym następuje górna granica.
Przykład
Poniższy przykład ilustruje, w jaki sposób extends jest używany do określenia górnej granicy symbolu wieloznacznego.
package com.tutorialspoint;
import java.util.Arrays;
import java.util.List;
public class GenericsTester {
public static double sum(List<? extends Number> numberlist) {
double sum = 0.0;
for (Number n : numberlist) sum += n.doubleValue();
return sum;
}
public static void main(String args[]) {
List<Integer> integerList = Arrays.asList(1, 2, 3);
System.out.println("sum = " + sum(integerList));
List<Double> doubleList = Arrays.asList(1.2, 2.3, 3.5);
System.out.println("sum = " + sum(doubleList));
}
}
To da następujący wynik -
Wynik
sum = 6.0
sum = 7.0
Znak zapytania (?) Reprezentuje symbol wieloznaczny, oznacza nieznany typ w rodzajach generycznych. Może się zdarzyć, że dowolny obiekt może zostać użyty, gdy metoda może zostać zaimplementowana przy użyciu funkcji udostępnionej w klasie Object lub gdy kod jest niezależny od parametru typu.
Aby zadeklarować parametr bez ograniczeń, należy wymienić? tylko.
Przykład
Poniższy przykład ilustruje sposób użycia rozszerzeń do określenia nieograniczonego symbolu wieloznacznego.
package com.tutorialspoint;
import java.util.Arrays;
import java.util.List;
public class GenericsTester {
public static void printAll(List<?> list) {
for (Object item : list)
System.out.println(item + " ");
}
public static void main(String args[]) {
List<Integer> integerList = Arrays.asList(1, 2, 3);
printAll(integerList);
List<Double> doubleList = Arrays.asList(1.2, 2.3, 3.5);
printAll(doubleList);
}
}
To da następujący wynik -
Wynik
1
2
3
1.2
2.3
3.5
Znak zapytania (?) Reprezentuje symbol wieloznaczny, oznacza nieznany typ w rodzajach generycznych. Może się zdarzyć, że będziesz chciał ograniczyć rodzaje typów, które mogą być przekazywane do parametru typu. Na przykład metoda operująca na liczbach może chcieć akceptować tylko wystąpienia typu Integer lub jej nadklasy, takie jak Number.
Aby zadeklarować parametr wieloznaczny ograniczający dolną granicę, wypisz?, Po którym następuje słowo kluczowe super, a po nim jego dolna granica.
Przykład
Poniższy przykład ilustruje, w jaki sposób super jest używany do określenia dolnej granicy symbolu wieloznacznego.
package com.tutorialspoint;
import java.util.ArrayList;
import java.util.List;
public class GenericsTester {
public static void addCat(List<? super Cat> catList) {
catList.add(new RedCat());
System.out.println("Cat Added");
}
public static void main(String[] args) {
List<Animal> animalList= new ArrayList<Animal>();
List<Cat> catList= new ArrayList<Cat>();
List<RedCat> redCatList= new ArrayList<RedCat>();
List<Dog> dogList= new ArrayList<Dog>();
//add list of super class Animal of Cat class
addCat(animalList);
//add list of Cat class
addCat(catList);
//compile time error
//can not add list of subclass RedCat of Cat class
//addCat(redCatList);
//compile time error
//can not add list of subclass Dog of Superclass Animal of Cat class
//addCat.addMethod(dogList);
}
}
class Animal {}
class Cat extends Animal {}
class RedCat extends Cat {}
class Dog extends Animal {}
To da następujący wynik -
Cat Added
Cat Added
Symbole wieloznaczne mogą być używane na trzy sposoby -
Upper bound Wildcard-? rozszerza Type.
Lower bound Wildcard-? super Type.
Unbounded Wildcard -?
Aby zdecydować, który typ symbolu wieloznacznego najlepiej pasuje do warunku, najpierw sklasyfikujmy typ parametrów przekazywanych do metody jako in i out parametr.
in variable- Zmienna in dostarcza dane do kodu. Na przykład copy (src, dest). Tutaj src działa jak zmienna będąca danymi do skopiowania.
out variable- Zmienna out przechowuje dane aktualizowane przez kod. Na przykład copy (src, dest). Tutaj dest działa jak w zmiennej po skopiowaniu danych.
Wytyczne dotyczące symboli wieloznacznych.
Upper bound wildcard - Jeśli zmienna ma wartość in category, użyj słowa kluczowego extends z symbolem wieloznacznym.
Lower bound wildcard - Jeśli zmienna ma wartość out category, użyj słowa kluczowego super z symbolem wieloznacznym.
Unbounded wildcard - Jeśli dostęp do zmiennej można uzyskać za pomocą metody klasy Object, użyj niezwiązanego symbolu wieloznacznego.
No wildcard - Jeśli kod uzyskuje dostęp do zmiennej w obu in i out category, to nie używaj symboli wieloznacznych.
Przykład
Poniższy przykład ilustruje powyższe koncepcje.
package com.tutorialspoint;
import java.util.ArrayList;
import java.util.List;
public class GenericsTester {
//Upper bound wildcard
//in category
public static void deleteCat(List<? extends Cat> catList, Cat cat) {
catList.remove(cat);
System.out.println("Cat Removed");
}
//Lower bound wildcard
//out category
public static void addCat(List<? super RedCat> catList) {
catList.add(new RedCat("Red Cat"));
System.out.println("Cat Added");
}
//Unbounded wildcard
//Using Object method toString()
public static void printAll(List<?> list) {
for (Object item : list)
System.out.println(item + " ");
}
public static void main(String[] args) {
List<Animal> animalList= new ArrayList<Animal>();
List<RedCat> redCatList= new ArrayList<RedCat>();
//add list of super class Animal of Cat class
addCat(animalList);
//add list of Cat class
addCat(redCatList);
addCat(redCatList);
//print all animals
printAll(animalList);
printAll(redCatList);
Cat cat = redCatList.get(0);
//delete cat
deleteCat(redCatList, cat);
printAll(redCatList);
}
}
class Animal {
String name;
Animal(String name) {
this.name = name;
}
public String toString() {
return name;
}
}
class Cat extends Animal {
Cat(String name) {
super(name);
}
}
class RedCat extends Cat {
RedCat(String name) {
super(name);
}
}
class Dog extends Animal {
Dog(String name) {
super(name);
}
}
To da następujący wynik -
Cat Added
Cat Added
Cat Added
Red Cat
Red Cat
Red Cat
Cat Removed
Red Cat
Typy generyczne są używane do dokładniejszego sprawdzania typów w czasie kompilacji i do zapewniania programowania ogólnego. Aby zaimplementować ogólne zachowanie, kompilator java stosuje wymazywanie typów. Typ wymazywania to proces, w którym kompilator zastępuje parametr ogólny rzeczywistą metodą klasy lub mostu. W przypadku wymazywania typów kompilator zapewnia, że nie są tworzone żadne dodatkowe klasy i nie ma narzutu czasu wykonywania.
Wpisz zasady wymazywania
Zastąp parametry typu w typie ogólnym ich powiązanymi, jeśli są używane parametry typu związanego.
Zastąp parametry typu w typie ogólnym Object, jeśli używane są nieograniczone parametry typu.
Odlewy typu insert, aby zachować bezpieczeństwo typu.
Generuj metody pomostowe, aby zachować polimorfizm w rozszerzonych typach ogólnych.
Kompilator Java zastępuje parametry typu w typie ogólnym ich powiązanymi, jeśli są używane parametry typu związanego.
Przykład
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<Double> doubleBox = new Box<Double>();
integerBox.add(new Integer(10));
doubleBox.add(new Double(10.0));
System.out.printf("Integer Value :%d\n", integerBox.get());
System.out.printf("Double Value :%s\n", doubleBox.get());
}
}
class Box<T extends Number> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
W takim przypadku kompilator java zastąpi T klasą Number i po usunięciu typu kompilator wygeneruje kod bajtowy dla następującego kodu.
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box integerBox = new Box();
Box doubleBox = new Box();
integerBox.add(new Integer(10));
doubleBox.add(new Double(10.0));
System.out.printf("Integer Value :%d\n", integerBox.get());
System.out.printf("Double Value :%s\n", doubleBox.get());
}
}
class Box {
private Number t;
public void add(Number t) {
this.t = t;
}
public Number get() {
return t;
}
}
W obu przypadkach wynik jest taki sam -
Wynik
Integer Value :10
Double Value :10.0
Kompilator Java zamienia parametry typu w typie ogólnym na Object, jeśli są używane niezwiązane parametry typu.
Przykład
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
integerBox.add(new Integer(10));
stringBox.add(new String("Hello World"));
System.out.printf("Integer Value :%d\n", integerBox.get());
System.out.printf("String Value :%s\n", stringBox.get());
}
}
class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
W takim przypadku kompilator java zastąpi T klasą Object i po usunięciu typu kompilator wygeneruje kod bajtowy dla następującego kodu.
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box integerBox = new Box();
Box stringBox = new Box();
integerBox.add(new Integer(10));
stringBox.add(new String("Hello World"));
System.out.printf("Integer Value :%d\n", integerBox.get());
System.out.printf("String Value :%s\n", stringBox.get());
}
}
class Box {
private Object t;
public void add(Object t) {
this.t = t;
}
public Object get() {
return t;
}
}
W obu przypadkach wynik jest taki sam -
Wynik
Integer Value :10
String Value :Hello World
Kompilator Java zamienia parametry typu w typie ogólnym na Object, jeśli są używane niezwiązane parametry typu, oraz typem, jeśli parametry powiązane są używane jako parametry metody.
Przykład
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
integerBox.add(new Integer(10));
stringBox.add(new String("Hello World"));
printBox(integerBox);
printBox1(stringBox);
}
private static <T extends Box> void printBox(T box) {
System.out.println("Integer Value :" + box.get());
}
private static <T> void printBox1(T box) {
System.out.println("String Value :" + ((Box)box).get());
}
}
class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
W takim przypadku kompilator java zastąpi T klasą Object i po usunięciu typu kompilator wygeneruje kod bajtowy dla następującego kodu.
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box integerBox = new Box();
Box stringBox = new Box();
integerBox.add(new Integer(10));
stringBox.add(new String("Hello World"));
printBox(integerBox);
printBox1(stringBox);
}
//Bounded Types Erasure
private static void printBox(Box box) {
System.out.println("Integer Value :" + box.get());
}
//Unbounded Types Erasure
private static void printBox1(Object box) {
System.out.println("String Value :" + ((Box)box).get());
}
}
class Box {
private Object t;
public void add(Object t) {
this.t = t;
}
public Object get() {
return t;
}
}
W obu przypadkach wynik jest taki sam -
Wynik
Integer Value :10
String Value :Hello World
Przy użyciu typów ogólnych nie można przekazywać typów pierwotnych jako parametrów typu. W przykładzie podanym poniżej, jeśli przekażemy typ pierwotny int do klasy box, kompilator będzie narzekał. Aby złagodzić to samo, musimy przekazać obiekt Integer zamiast typu pierwotnego int.
Przykład
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
//compiler errror
//ReferenceType
//- Syntax error, insert "Dimensions" to complete
ReferenceType
//Box<int> stringBox = new Box<int>();
integerBox.add(new Integer(10));
printBox(integerBox);
}
private static void printBox(Box box) {
System.out.println("Value: " + box.get());
}
}
class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
To da następujący wynik -
Wynik
Value: 10
Nie można użyć parametru typu do utworzenia wystąpienia obiektu wewnątrz metody.
public static <T> void add(Box<T> box) {
//compiler error
//Cannot instantiate the type T
//T item = new T();
//box.add(item);
}
Aby osiągnąć taką funkcjonalność, użyj refleksji.
public static <T> void add(Box<T> box, Class<T> clazz)
throws InstantiationException, IllegalAccessException{
T item = clazz.newInstance(); // OK
box.add(item);
System.out.println("Item added.");
}
Przykład
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args)
throws InstantiationException, IllegalAccessException {
Box<String> stringBox = new Box<String>();
add(stringBox, String.class);
}
public static <T> void add(Box<T> box) {
//compiler error
//Cannot instantiate the type T
//T item = new T();
//box.add(item);
}
public static <T> void add(Box<T> box, Class<T> clazz)
throws InstantiationException, IllegalAccessException{
T item = clazz.newInstance(); // OK
box.add(item);
System.out.println("Item added.");
}
}
class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
To da następujący wynik -
Item added.
Używając typów ogólnych, parametry typu nie mogą być statyczne. Ponieważ zmienna statyczna jest dzielona między obiektami, kompilator nie może określić, jakiego typu użyć. Rozważ poniższy przykład, jeśli dozwolone były statyczne parametry typu.
Przykład
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
integerBox.add(new Integer(10));
printBox(integerBox);
}
private static void printBox(Box box) {
System.out.println("Value: " + box.get());
}
}
class Box<T> {
//compiler error
private static T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
Ponieważ stringBox i integerBox mają zmienną typu statycznego, nie można określić jej typu. Dlatego statyczne parametry typu nie są dozwolone.
Rzutowanie na typ sparametryzowany jest niedozwolone, chyba że jest sparametryzowane przez nieograniczone symbole wieloznaczne.
Box<Integer> integerBox = new Box<Integer>();
Box<Number> numberBox = new Box<Number>();
//Compiler Error: Cannot cast from Box<Number> to Box<Integer>
integerBox = (Box<Integer>)numberBox;
Aby uzyskać to samo, można użyć nieograniczonych symboli wieloznacznych.
private static void add(Box<?> box) {
Box<Integer> integerBox = (Box<Integer>)box;
}
Ponieważ kompilator używa wymazywania typu, środowisko uruchomieniowe nie śledzi parametrów typu, więc w czasie wykonywania różnicy między Box <Integer> i Box <String> nie można zweryfikować za pomocą operatora instanceOf.
Box<Integer> integerBox = new Box<Integer>();
//Compiler Error:
//Cannot perform instanceof check against
//parameterized type Box<Integer>.
//Use the form Box<?> instead since further
//generic type information will be erased at runtime
if(integerBox instanceof Box<Integer>) { }
Tablice typów sparametryzowanych są niedozwolone.
//Cannot create a generic array of Box<Integer>
Box<Integer>[] arrayOfLists = new Box<Integer>[2];
Ponieważ kompilator używa wymazywania typu, parametr typu jest zastępowany przez Object, a użytkownik może dodać dowolny typ obiektu do tablicy. W czasie wykonywania kod nie będzie mógł zgłosić wyjątku ArrayStoreException.
// compiler error, but if it is allowed
Object[] stringBoxes = new Box<String>[];
// OK
stringBoxes[0] = new Box<String>();
// An ArrayStoreException should be thrown,
//but the runtime can't detect it.
stringBoxes[1] = new Box<Integer>();
Klasa ogólna nie może bezpośrednio ani pośrednio rozszerzać klasy Throwable.
//The generic class Box<T> may not subclass java.lang.Throwable
class Box<T> extends Exception {}
//The generic class Box<T> may not subclass java.lang.Throwable
class Box1<T> extends Throwable {}
Metoda nie może przechwytywać wystąpienia parametru typu.
public static <T extends Exception, J>
void execute(List<J> jobs) {
try {
for (J job : jobs) {}
// compile-time error
//Cannot use the type parameter T in a catch block
} catch (T e) {
// ...
}
}
Parametry typu są dozwolone w klauzuli throws.
class Box<T extends Exception> {
private int t;
public void add(int t) throws T {
this.t = t;
}
public int get() {
return t;
}
}
Klasa nie może mieć dwóch przeciążonych metod, które mogą mieć ten sam podpis po usunięciu typu.
class Box {
//Compiler error
//Erasure of method print(List<String>)
//is the same as another method in type Box
public void print(List<String> stringList) { }
public void print(List<Integer> integerList) { }
}