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) { }
}