Génériques Java - Guide rapide

Ce serait bien si nous pouvions écrire une méthode de tri unique qui pourrait trier les éléments dans un tableau Integer, un tableau String ou un tableau de tout type prenant en charge la commande.

Les méthodes génériques Java et les classes génériques permettent aux programmeurs de spécifier, avec une seule déclaration de méthode, un ensemble de méthodes associées, ou avec une seule déclaration de classe, un ensemble de types associés, respectivement.

Les génériques fournissent également une sécurité de type au moment de la compilation qui permet aux programmeurs de détecter les types non valides au moment de la compilation.

En utilisant le concept Java Generic, nous pourrions écrire une méthode générique pour trier un tableau d'objets, puis appeler la méthode générique avec des tableaux Integer, des tableaux doubles, des tableaux de chaînes et ainsi de suite, pour trier les éléments du tableau.

Configuration de l'environnement local

JUnit est un framework pour Java, donc la toute première exigence est d'avoir JDK installé sur votre machine.

Exigence du système

JDK 1.5 ou supérieur.
Mémoire Aucune exigence minimale.
Espace disque Aucune exigence minimale.
Système opérateur Aucune exigence minimale.

Étape 1: Vérifiez l'installation de Java sur votre machine

Tout d'abord, ouvrez la console et exécutez une commande java basée sur le système d'exploitation sur lequel vous travaillez.

OS Tâche Commander
les fenêtres Ouvrez la console de commande c: \> java -version
Linux Ouvrir le terminal de commande $ java -version
Mac Terminal ouvert machine: <joseph $ java -version

Vérifions la sortie pour tous les systèmes d'exploitation -

OS Production
les fenêtres

version java "1.6.0_21"

Environnement d'exécution Java (TM) SE (build 1.6.0_21-b07)

VM client Java HotSpot (TM) (build 17.0-b17, mode mixte, partage)

Linux

version java "1.6.0_21"

Environnement d'exécution Java (TM) SE (build 1.6.0_21-b07)

VM client Java HotSpot (TM) (build 17.0-b17, mode mixte, partage)

Mac

version java "1.6.0_21"

Environnement d'exécution Java (TM) SE (build 1.6.0_21-b07)

VM serveur 64 bits Java HotSpot (TM) (build 17.0-b17, mode mixte, partage)

Si Java n'est pas installé sur votre système, téléchargez le kit de développement logiciel Java (SDK) à partir du lien suivant https://www.oracle.com. Nous supposons que Java 1.6.0_21 est la version installée pour ce didacticiel.

Étape 2: définir l'environnement JAVA

Met le JAVA_HOMEvariable d'environnement pour pointer vers l'emplacement du répertoire de base où Java est installé sur votre machine. Par exemple.

OS Production
les fenêtres Définissez la variable d'environnement JAVA_HOME sur C: \ Program Files \ Java \ jdk1.6.0_21
Linux export JAVA_HOME = / usr / local / java-current
Mac export JAVA_HOME = / Bibliothèque / Java / Accueil

Ajoutez l'emplacement du compilateur Java au chemin système.

OS Production
les fenêtres Ajouter la chaîne C:\Program Files\Java\jdk1.6.0_21\bin à la fin de la variable système, Path.
Linux export PATH = $PATH:$JAVA_HOME / bin /
Mac non requis

Vérifiez l'installation de Java à l'aide de la commande java -version comme expliqué ci-dessus.

Une déclaration de classe générique ressemble à une déclaration de classe non générique, sauf que le nom de classe est suivi d'une section de paramètre de type.

La section de paramètre de type d'une classe générique peut avoir un ou plusieurs paramètres de type séparés par des virgules. Ces classes sont appelées classes paramétrées ou types paramétrés car elles acceptent un ou plusieurs paramètres.

Syntaxe

public class Box<T> {
   private T t;
}

  • Box - Box est une classe générique.

  • T- Le paramètre de type générique passé à la classe générique. Cela peut prendre n'importe quel objet.

  • t - Instance de type générique T.

La description

Le T est un paramètre de type passé à la classe générique Box et doit être passé lors de la création d'un objet Box.

Exemple

Créez le programme Java suivant en utilisant n'importe quel éditeur de votre choix.

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;
   }   
}

Cela produira le résultat suivant.

Production

Integer Value :10
String Value :Hello World

Par convention, les noms de paramètres de type sont nommés sous forme de lettres majuscules uniques afin qu'un paramètre de type puisse être facilement distingué avec un nom de classe ou d'interface ordinaire. Voici la liste des noms de paramètres de type couramment utilisés -

  • E - Élément, et est principalement utilisé par le framework Java Collections.

  • K - Clé, et est principalement utilisé pour représenter le type de paramètre de clé d'une carte.

  • V - Valeur, et est principalement utilisé pour représenter le type de paramètre de valeur d'une carte.

  • N - Nombre, et est principalement utilisé pour représenter des nombres.

  • T - Type, et est principalement utilisé pour représenter le premier paramètre de type générique.

  • S - Type, et est principalement utilisé pour représenter le deuxième paramètre de type générique.

  • U - Type, et est principalement utilisé pour représenter le troisième paramètre de type générique.

  • V - Type, et est principalement utilisé pour représenter le quatrième paramètre de type générique.

L'exemple suivant présentera le concept mentionné ci-dessus.

Exemple

Créez le programme Java suivant en utilisant n'importe quel éditeur de votre choix.

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

Cela produira le résultat suivant.

Production

Integer Value :10
String Value :Hello World
(Pair)Integer Value :10
(CustomList)Integer Value :10

L'inférence de type représente la capacité du compilateur Java à examiner un appel de méthode et sa déclaration correspondante pour vérifier et déterminer le ou les arguments de type. L'algorithme d'inférence vérifie les types des arguments et, si disponible, le type attribué est renvoyé. Les algorithmes d'inférence essaient de trouver un type spécifique qui peut remplir tous les paramètres de type.

Le compilateur génère un avertissement de conversion non cochée dans le cas où l'inférence de type n'est pas utilisée.

Syntaxe

Box<Integer> integerBox = new Box<>();

  • Box - Box est une classe générique.

  • <> - L'opérateur losange désigne l'inférence de type.

La description

À l'aide de l'opérateur diamant, le compilateur détermine le type du paramètre. Cet opérateur est disponible à partir de la version Java SE 7.

Exemple

Créez le programme Java suivant en utilisant n'importe quel éditeur de votre choix.

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;
   }   
}

Cela produira le résultat suivant.

Production

Integer Value :10
String Value :Hello World

Vous pouvez écrire une seule déclaration de méthode générique qui peut être appelée avec des arguments de différents types. En fonction des types d'arguments passés à la méthode générique, le compilateur gère chaque appel de méthode de manière appropriée. Voici les règles pour définir les méthodes génériques -

  • Toutes les déclarations de méthode générique ont une section de paramètre de type délimitée par des crochets (<et>) qui précède le type de retour de la méthode (<E> dans l'exemple suivant).

  • Chaque section de paramètre de type contient un ou plusieurs paramètres de type séparés par des virgules. Un paramètre de type, également appelé variable de type, est un identificateur qui spécifie un nom de type générique.

  • Les paramètres de type peuvent être utilisés pour déclarer le type de retour et agir comme des espaces réservés pour les types des arguments passés à la méthode générique, appelés arguments de type réels.

  • Le corps d'une méthode générique est déclaré comme celui de toute autre méthode. Notez que les paramètres de type ne peuvent représenter que des types de référence, pas des types primitifs (comme int, double et char).

Exemple

L'exemple suivant illustre comment nous pouvons imprimer un tableau de type différent en utilisant une seule méthode générique -

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

Cela produira le résultat suivant -

Production

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

Une classe générique peut avoir plusieurs paramètres de type. L'exemple suivant présentera le concept mentionné ci-dessus.

Exemple

Créez le programme Java suivant en utilisant n'importe quel éditeur de votre choix.

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;
   } 
}

Cela produira le résultat suivant.

Production

Integer Value :10
String Value :Hello World
String Value :Message
String Value :Hello World

Une classe générique peut avoir des types paramétrés où un paramètre de type peut être remplacé par un type paramétré. L'exemple suivant présentera le concept mentionné ci-dessus.

Exemple

Créez le programme Java suivant en utilisant n'importe quel éditeur de votre choix.

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;
   } 
}

Cela produira le résultat suivant.

Production

Integer Value :10
String Value :[Hi, Hello, Bye]

Un type brut est un objet d'une classe ou d'une interface générique si ses arguments de type ne sont pas passés lors de sa création. L'exemple suivant présentera le concept mentionné ci-dessus.

Exemple

Créez le programme Java suivant en utilisant n'importe quel éditeur de votre choix.

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;
   } 
}

Cela produira le résultat suivant.

Production

Integer Value :10
Integer Value :10
Integer Value :10
Integer Value :10

Il peut arriver que vous souhaitiez restreindre les types de types autorisés à être transmis à un paramètre de type. Par exemple, une méthode qui opère sur des nombres peut vouloir uniquement accepter des instances de Number ou de ses sous-classes. C'est à cela que servent les paramètres de type borné.

Pour déclarer un paramètre de type borné, répertoriez le nom du paramètre de type, suivi du mot clé extend, suivi de sa limite supérieure.

Exemple

L'exemple suivant illustre comment étend est utilisé dans un sens général pour signifier "étend" (comme dans les classes) ou "implémente" (comme dans les interfaces). Cet exemple est une méthode générique pour renvoyer le plus grand des trois objets comparables -

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"));
   }
}

Cela produira le résultat suivant -

Production

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

Un paramètre de type peut avoir plusieurs limites.

Syntaxe

public static <T extends Number & Comparable<T>> T maximum(T x, T y, T z)

  • maximum - maximum est une méthode générique.

  • T- Le paramètre de type générique passé à la méthode générique. Cela peut prendre n'importe quel objet.

La description

Le T est un paramètre de type passé à la classe générique Box et doit être un sous-type de la classe Number et doit implémenter l'interface Comparable. Dans le cas où une classe est passée comme liée, elle doit être transmise en premier avant l'interface, sinon une erreur de compilation se produira.

Exemple

Créez le programme Java suivant en utilisant n'importe quel éditeur de votre choix.

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;   
   }*/
}

Cela produira le résultat suivant -

Production

Max of 3, 4 and 5 is 5

Max of 6.6,8.8 and 7.7 is 8.8

Java a fourni un support générique dans l'interface de liste.

Syntaxe

List<T> list = new ArrayList<T>();

  • list - objet de l'interface de liste.

  • T - Le paramètre de type générique passé lors de la déclaration de liste.

La description

Le T est un paramètre de type passé à l'interface générique List et à sa classe d'implémentation ArrayList.

Exemple

Créez le programme Java suivant en utilisant n'importe quel éditeur de votre choix.

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());
      }
   }  
}

Cela produira le résultat suivant -

Production

Integer Value :10
String Value :Hello World
Integer Value :10
Integer Value :11
String Value :Hello World
String Value :Hi World

Java a fourni un support générique dans l'interface Set.

Syntaxe

Set<T> set = new HashSet<T>();

  • set - objet de Set Interface.

  • T - Le paramètre de type générique passé lors de la déclaration d'ensemble.

La description

Le T est un paramètre de type passé à l'interface générique Set et à sa classe d'implémentation HashSet.

Exemple

Créez le programme Java suivant en utilisant n'importe quel éditeur de votre choix.

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());
      }
   }  
}

Cela produira le résultat suivant -

Production

Integer Value :10
Integer Value :11
String Value :Hello World
String Value :Hi World

Java a fourni un support générique dans l'interface Map.

Syntaxe

Set<T> set = new HashSet<T>();

  • set - objet de Set Interface.

  • T - Le paramètre de type générique passé lors de la déclaration d'ensemble.

La description

Le T est un paramètre de type passé à l'interface générique Set et à sa classe d'implémentation HashSet.

Exemple

Créez le programme Java suivant en utilisant n'importe quel éditeur de votre choix.

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());
      }
   }  
}

Cela produira le résultat suivant -

Production

Integer Value :10
String Value :Hello World
Integer Value :1
Integer Value :2
String Value :Hello World
String Value :Hi World

Le point d'interrogation (?), Représente le caractère générique, représente le type inconnu dans les génériques. Il peut arriver que vous souhaitiez restreindre les types de types autorisés à être transmis à un paramètre de type. Par exemple, une méthode qui opère sur des nombres peut vouloir uniquement accepter des instances de Number ou de ses sous-classes.

Pour déclarer un paramètre Wildcard avec limite supérieure, indiquez le?, Suivi du mot clé extend, suivi de sa limite supérieure.

Exemple

L'exemple suivant illustre comment extend est utilisé pour spécifier un caractère générique de limite supérieure.

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

Cela produira le résultat suivant -

Production

sum = 6.0
sum = 7.0

Le point d'interrogation (?), Représente le caractère générique, représente le type inconnu dans les génériques. Il peut arriver qu'un objet puisse être utilisé lorsqu'une méthode peut être implémentée à l'aide des fonctionnalités fournies dans la classe Object ou lorsque le code est indépendant du paramètre type.

Pour déclarer un paramètre Wildcard sans limite, répertoriez le? seulement.

Exemple

L'exemple suivant illustre comment extend est utilisé pour spécifier un caractère générique illimité.

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

Cela produira le résultat suivant -

Production

1 
2 
3 
1.2 
2.3 
3.5

Le point d'interrogation (?), Représente le caractère générique, représente le type inconnu dans les génériques. Il peut arriver que vous souhaitiez restreindre les types de types autorisés à être transmis à un paramètre de type. Par exemple, une méthode qui opère sur des nombres peut ne vouloir accepter que des instances d'Integer ou de ses superclasses comme Number.

Pour déclarer un paramètre Wildcard à limite inférieure, indiquez le?, Suivi du mot clé super, suivi de sa limite inférieure.

Exemple

L'exemple suivant illustre comment super est utilisé pour spécifier un caractère générique de limite inférieure.

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

Cela produira le résultat suivant -

Cat Added
Cat Added

Les caractères génériques peuvent être utilisés de trois manières:

  • Upper bound Wildcard-? étend le type.

  • Lower bound Wildcard-? Super Type.

  • Unbounded Wildcard -?

Afin de décider quel type de caractère générique convient le mieux à la condition, classons d'abord le type de paramètres transmis à une méthode comme in et out paramètre.

  • in variable- Une variable in fournit des données au code. Par exemple, copiez (src, dest). Ici, src agit comme une variable étant des données à copier.

  • out variable- Une variable out contient les données mises à jour par le code. Par exemple, copiez (src, dest). Ici dest agit comme une variable ayant copié des données.

Directives pour les caractères génériques.

  • Upper bound wildcard - Si une variable est de in category, utilisez le mot-clé extend avec un caractère générique.

  • Lower bound wildcard - Si une variable est de out catégorie, utilisez un super mot-clé avec un caractère générique.

  • Unbounded wildcard - Si une variable est accessible à l'aide de la méthode de la classe Object, utilisez un caractère générique indépendant.

  • No wildcard - Si le code accède à la variable dans les deux in et out catégorie alors n'utilisez pas de caractères génériques.

Exemple

L'exemple suivant illustre les concepts mentionnés ci-dessus.

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

Cela produira le résultat suivant -

Cat Added
Cat Added
Cat Added
Red Cat 
Red Cat 
Red Cat 
Cat Removed
Red Cat

Les génériques sont utilisés pour des vérifications de type plus strictes au moment de la compilation et pour fournir une programmation générique. Pour implémenter un comportement générique, le compilateur java applique l'effacement de type. L'effacement de type est un processus dans lequel le compilateur remplace un paramètre générique par une classe réelle ou une méthode de pont. Dans l'effacement de type, le compilateur garantit qu'aucune classe supplémentaire n'est créée et qu'il n'y a pas de surcharge d'exécution.

Règles d'effacement de type

  • Remplacez les paramètres de type dans le type générique par leur limite si des paramètres de type borné sont utilisés.

  • Remplacez les paramètres de type dans le type générique par Object si des paramètres de type illimités sont utilisés.

  • Insérez des moulages de type pour préserver la sécurité du type.

  • Générez des méthodes de pont pour conserver le polymorphisme dans les types génériques étendus.

Le compilateur Java remplace les paramètres de type dans le type générique par leur limite si des paramètres de type borné sont utilisés.

Exemple

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;
   }   
}

Dans ce cas, le compilateur java remplacera T par la classe Number et après l'effacement du type, le compilateur générera du bytecode pour le code suivant.

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;
   }   
}

Dans les deux cas, le résultat est le même -

Production

Integer Value :10
Double Value :10.0

Java Compiler remplace les paramètres de type dans le type générique par Object si des paramètres de type illimités sont utilisés.

Exemple

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;
   }   
}

Dans ce cas, le compilateur java remplacera T par la classe Object et après l'effacement du type, le compilateur générera du bytecode pour le code suivant.

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;
   }   
}

Dans les deux cas, le résultat est le même -

Production

Integer Value :10
String Value :Hello World

Le compilateur Java remplace les paramètres de type dans le type générique par Object si des paramètres de type illimités sont utilisés et par type si des paramètres liés sont utilisés comme paramètres de méthode.

Exemple

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;
   }   
}

Dans ce cas, le compilateur java remplacera T par la classe Object et après l'effacement du type, le compilateur générera du bytecode pour le code suivant.

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;
   }   
}

Dans les deux cas, le résultat est le même -

Production

Integer Value :10
String Value :Hello World

En utilisant des génériques, les types primitifs ne peuvent pas être passés en tant que paramètres de type. Dans l'exemple donné ci-dessous, si nous passons le type primitif int à la classe box, alors le compilateur se plaindra. Pour atténuer la même chose, nous devons passer l'objet Integer au lieu du type primitif int.

Exemple

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;
   }   
}

Cela produira le résultat suivant -

Production

Value: 10

Un paramètre de type ne peut pas être utilisé pour instancier son objet dans une méthode.

public static <T> void add(Box<T> box) {
   //compiler error
   //Cannot instantiate the type T
   //T item = new T();  
   //box.add(item);
}

Pour obtenir une telle fonctionnalité, utilisez la réflexion.

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.");
}

Exemple

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;
   }   
}

Cela produira le résultat suivant -

Item added.

En utilisant des génériques, les paramètres de type ne sont pas autorisés à être statiques. Comme la variable statique est partagée entre les objets, le compilateur ne peut pas déterminer le type à utiliser. Prenons l'exemple suivant si les paramètres de type statique étaient autorisés.

Exemple

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;
   }   
}

Comme stringBox et integerBox ont tous deux une variable de type statique en étoile, son type ne peut pas être déterminé. Par conséquent, les paramètres de type statique ne sont pas autorisés.

La conversion en un type paramétré n'est pas autorisée à moins qu'elle ne soit paramétrée par des caractères génériques illimités.

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;

Pour obtenir la même chose, des caractères génériques illimités peuvent être utilisés.

private static void add(Box<?> box) {
   Box<Integer> integerBox = (Box<Integer>)box;
}

Étant donné que le compilateur utilise l'effacement de type, le runtime ne conserve pas la trace des paramètres de type, donc à l'exécution, la différence entre Box <Integer> et Box <String> ne peut pas être vérifiée à l'aide de l'opérateur 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>) { }

Les tableaux de types paramétrés ne sont pas autorisés.

//Cannot create a generic array of Box<Integer>
Box<Integer>[] arrayOfLists = new Box<Integer>[2];

Étant donné que le compilateur utilise l'effacement de type, le paramètre de type est remplacé par Object et l'utilisateur peut ajouter n'importe quel type d'objet au tableau. Et au moment de l'exécution, le code ne pourra pas lancer 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>();

Une classe générique n'est pas autorisée à étendre la classe Throwable directement ou indirectement.

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

Une méthode n'est pas autorisée à intercepter une instance d'un paramètre de type.

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

Les paramètres de type sont autorisés dans une clause throws.

class Box<T extends Exception>  {
   private int t;

   public void add(int t) throws T {
      this.t = t;
   }

   public int get() {
      return t;
   }   
}

Une classe n'est pas autorisée à avoir deux méthodes surchargées qui peuvent avoir la même signature après l'effacement du type.

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