Les génériques (generics)
DEST_DIR="Documents/my_brain_obs/"
Status:
Tags: <% tp.file.cursor(3) %>
Links: <% tp.file.cursor(4) %>
Les génériques (generics)
<% tp.file.cursor(5) %>
introduction aux Les génériques (generics) en java
Problématique de la généricité.
- Les versions de Java antérieures à 1.5 permettaient de créer des classes de structures contenant n'importe quels types d'objet :
- Cas des collections
- Cas des classes du programmeur travaillant sur des instances d'Object
Problèmes
- Utilisation massive du "downcasting".
- Risque d'erreur de cast ("ClassCastException").
- Impossibilité pour une collection de contenir un seul type d'objet.
Avant java 1.5
import java.util.*;
public class SansGenerique {
public static void main(String[] args) {
List liste = new ArrayList();
String valeur = null;
for(int i = 0; i < 10; i++) { valeur = ""+i; liste.add(valeur); }
for (Iterator iter = liste.iterator(); iter.hasNext(); ) {
valeur = (String) iter.next(); // Utilisation du Cast
System.out.println(valeur.toUpperCase());
}
}
}
Constatations
- Le cast ne peut être vérifié qu'à l'exécution;
- Possibilité d’échec en levant une ClassCastException.
- Avec l'utilisation des génériques, la vérification se fait lors de la phase de compilation :
- la sécurité du code est ainsi renforcée.
Après java 1.5
import java.util.*;
public class AvecGenerique {
public static void main(String[] args) {
List<String> liste = new ArrayList();
String valeur = null;
for(int i = 0; i < 10; i++) { valeur = ""+i; liste.add(valeur); }
for (Iterator<String> iter = liste.iterator(); iter.hasNext(); ) {
System.out.println(iter.next().toUpperCase());
}
}
}
Le besoin des génériques
- De nombreuses classes de l'API Collection possèdaient des paramètres de type Object et renvoient les valeurs des méthodes sous forme d'Object.
- Elles permettent donc de contenir des valeurs hétérogènes.
- Pour garantir que seul un type d'objet est ajouté dans la collection, il fallait tester le type avant d'invoquer les méthodes d'ajouts.
import java.util.List; import java.util.ArrayList;
import java.util.Iterator;
public class MaClasse {
public static void main(String[] args) {
List liste = new ArrayList();
liste.add("test");
Iterator it = liste.iterator();
while(it.hasNext()) {
String valeur = it.next();
System.out.println(valeur);
}
}
}
Après Compilation
- error: incompatible types: Object cannot be converted to String
- DownCating implicite
- Il faut donc obligatoirement utiliser un downCasting explicite vers le type d'objet pour pouvoir le manipuler.
String valeur = (String) it.next();
Autre problème
Cela n'empêche cependant pas d'ajouter des objets d'un autre type et donc d'avoir une exception de type CastClassException à l'exécution.
liste.add("test");
liste.add(new Integer(1));
L'apport des génériques
- Les génériques permettent de définir des classes, des interfaces, des records et des méthodes qui utilisent un type précisé à la création de l'instance ou à l'invocation de la méthode.
- La définition et l'utilisation d'un type générique se fait en utilisant l'opérateur diamant <> dans lequel on précise le type à utiliser.
- L'utilisation des génériques améliore significativement la robustesse du code et le rend plus lisible.
Les génériques et l'API Collection.
- Avant Java 5, il est possible d'ajouter des instances de différents types dans une collection
- Après Java 5, l'API Collection a été revue pour utiliser les génériques .
List<String> liste = new ArrayList<String>();
L’idée derrière les génériques
- Les génériques signifient des types paramétrés.
Exemple :
ArrayList<T>
- C’est-à-dire, permettre au type d'être un paramètre pour les méthodes, les classes et les interfaces.
- En les utilisant, il est possible de créer des classes qui fonctionnent avec différents types de données.
Vocabulaire s utilisés pour les génériques
- un type générique (generic type) est une classe, une interface
ou un record, par Exemple :
ArrayList<T>
- un paramètre de type (type parameter) ou variable de type :
exemple T dans
ArrayList<T>
- un type paramétré (parameterized type) est une classe,
par exemple,
ArrayList<Long>
- un argument de type (type argument) est le type précisé, par
exemple Long dans
ArrayList<Long>
- un type brut (raw type) est un type générique qui n'est paramétré
avec rien, comme new
ArrayList()
L'utilisation de types génériques
Les génériques peuvent être utilisés avec :
- des types (classes, interfaces, records)
- des méthodes et des constructeurs
// Création d’un type générique
public class MaClasseGenerique<T1, T2> {
private T1 param1; private T2 param2;
public MaClasseGenerique(T1 param1, T2 param2) {
this.param1 = param1; this.param2 = param2;
}
public T1 getParam1() {return this.param1;}
public T2 getParam2() {return this.param2;}
}
import java.util.*;
public class TestClasseGenerique {
public static void main(String[] args) {
MaClasseGenerique<Integer, String> maClasse = new MaClasseGenerique<Integer, String>(1, "valeur 1");
Integer param1 = maClasse.getParam1();
String param2 = maClasse.getParam2();
}
}
L'opérateur diamant
// Avant Java 7, il était obligatoire,
MaClasseGenerique<Integer, String> maClasse = new MaClasseGenerique<Integer, String>(1, "valeur 1");
// Après Java 7
MaClasseGenerique<Integer, String> maClasse = new MaClasseGenerique<>(1, "valeur 1");
// ou
MaClasseGenerique<Integer, String> maClasse = new MaClasseGenerique(1, "valeur 1");
Voir Exemple de classe interne anonyme dans :
MainOperation.java
public class MainOperation {
public static void main(String[] args) {
//Non Obligatoire après la version java 9 lors de
// l'utilisation d'une classe interne anonyme
Operation<Integer> op = new Operation<>() {
public Integer ajouter(Integer a, Integer b) {
return a+b;
}
};
}
}
Utilisation de "raw type"
Le rôle des types brutes est de conserver la rétrocompatibilité avec le code antérieure à Java 5 : il n'est donc pas recommandé de les utiliser dans un autre contexte.
Voir Exemple de warning (raw type) avec :
javac -Xlint UtilisationGeneriques.java
L'exemple Test.java génère plusieurs avertissements par le compilateur.
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
List nombres = new ArrayList<Integer>();
nombres.add(1);
List chaines = new ArrayList<String>();
chaines.add("1");
List<String> liste;
liste = chaines;
String chaine = liste.get(0);
liste = nombres;
chaine = liste.get(0);
}
}
Le dernier warning génère même une erreur d’exécution de Type ClassCastException.
Donc, Mixer des types bruts et des paramétrés doit être évité
// Définition de type générique
class Test<T> {
T obj;
Test (T obj) { this.obj = obj; }
public T getObject() { return this.obj; }
}
// Example of an instance of generic class
class Main {
public static void main (String[] args) {
// instance of Integer type
Test <Integer> obj = new Test<Integer>(15);
System.out.println(obj.getObject());
// instance of String type
Test <String> sObj = new Test<String>("Hello");
System.out.println(sObj.getObject());
}
}
Dans le JDK, les noms des paramètres de types couramment
utilisés sont :
- T : un type comme premier paramètre
- E : un élément notamment dans les types de l'API Collection
- N : un nombre
- S, U V : un type comme paramètre supplémentaire
- K : la clé d'une Map
- V : la valeur d'une map
Les génériques et l'héritage
il n'y a pas de relation entre deux types paramétrés même si les arguments de types utilisés possèdent une relation.
Exemple :
import java.util.List; import java.util.ArrayList;
public class GenHer {
public static void main(String[] args) {
List<Number> nombres = new ArrayList<Number>();
List<Integer> entiers = new ArrayList<Integer>();
nombres = entiers; // Erreur de Compilation
}
}
Les classes génériques et le sous-typage
Il est possible de sous-typer une classe ou une interface générique en l'étendant ou en l'implémentant.
Une classe qui hérite d'une classe générique peut spécifier les arguments de type, peut conserver les paramètres de type ou ajouter des paramètres de type supplémentaire.
Voir Exemple :
interface MaList<E>
Il est possible d'hériter d'un type paramétré.
Exemple
class A implements I <Integer, String> { .. }
Il est possible d'hériter d'un type générique mixant paramètre de type et argument de type .
Exemple
class A<V> implements I<Integer, V> { .. }
Le transtypage des instances génériques
Il n'est pas possible non plus de caster l'affectation d'une variable d'un type générique même si c'est vers une variable d'un même type et d'un type générique qui soit un sous-type.
Exemple :
import java.util.ArrayList; import java.util.List;
public class MaClasse<T> {
public static void main(String... args) {
List<String> chaines = new ArrayList<String>();
List<Object> objets = (List<Object>) chaines;
objets.add(new Object());
}
}
Les méthodes et les constructeurs génériques
Parfois, la classe ne doit pas être générique mais seulement une méthode.
public class MethodeGen1 {
public static < E > void afficher( E[] donnees ) {
for(E element : donnees) { System.out.print(element); }
System.out.println(); }
public static void main(String args[]) {
Integer[] entiers = { 1, 2, 3, 4, 5 };
String[] chaines = { "a", "b", "c", "d", "e" };
afficher(entiers); afficher(chaines); }
}
}
public class MethodeGen2 {
public static <T extends Comparable<T>> T max(T x, T y) {
T max = x;
if(y.compareTo(max) > 0) {max = y;}
return max;
}
public static void main(String... args) {
System.out.println(MethodeGen2.max(123, 26));
System.out.println(MethodeGen2.max("abc", "xyz"));
}
}
}
Il est aussi possible et parfois nécessaire d'indiquer explicitement le type générique notamment si le compilateur ne peut pas l'inférer.
public class MethodeGen3 {
public static <T extends Comparable<T>> T max(T x, T y) {
T max = x; if(y.compareTo(max) > 0) {max = y;} return max;
}
public static void main(String[] args) {
System.out.println(MethodeGen3.max(123, 26));
System.out.println(MethodeGen3.max("abc", "xyz"));
System.out.println(MethodeGen3.<Integer>max(123, 26));
System.out.println(MethodeGen3.<String>max("abc", "xyz"));
}
}
Les paramètres de type bornés (bounded type parameters))
Ce mécanisme permet une utilisation un peu moins stricte du typage dans les génériques.
import java.util.*;
public class MaClasseGenerique1<T extends Collection> {
private T param;
public MaClasseGenerique1(T param) {
this.param = param;
}
public T getParam() {return this.param;}
}
import java.util.*;
public class TestClasseGenerique1 {
public static void main(String[] args) {
MaClasseGenerique1<ArrayList> maClasseA = new MaClasseGenerique1<ArrayList>(new ArrayList());
MaClasseGenerique1<TreeSet> maClasseB = new MaClasseGenerique1<TreeSet>(new TreeSet());
}
}
restreindre le type d'objets qui peut être utilisé dans le type paramétré dans une méthode.
Exemple :
public class MaClasse<T> {
public static <T extends Comparable<T>> int comparer(T t1, T t2){
return t1.compareTo(t2);
}
}
Les paramètres de type avec wildcard)
Toute tentative d'utiliser un autre type même d'un sous-type provoque une erreur à la compilation
import java.util.ArrayList;
public class UtilisationGeneriques {
public static void main(String[] args) {
ArrayList<Integer> listInteger = new ArrayList<>();
// Erreur de compilation
ArrayList<Number> listNumber = listInteger;
}
}
Pour permettre de contourner ces limitations et gagner en flexibilité Un "wildcard" désigne un type quelconque.
Seulement, Les types paramétrés avec un wildcard imposent des restrictions par rapport aux types paramétrés avec un type concret :
- il n'est pas possible de créer une instance, par exemple :
new ArrayList<?> `
- il n'est pas possible d'hériter d'un type paramétré avec un wildcard, par exemple :
interface MaList extends List<?>
Exemples
Set
Cependant, à l'exécution, cet ensemble devra être assignée à un type concret.
·
List<
·
· Comparator<
Exemple
Dans l'API Collections, la méthode addAll() de l'interface
Collection permet d'ajouter tous les éléments de la Collection passée en paramètre et contenant des éléments du type de la collection ou d'un de ses sous-types.
La signature de la méthode est de la forme :
boolean addAll(Collection<? extends E> c)
Voir Exemple WilCard2.java
import java.util.List;
import java.util.ArrayList;
public class WilCard2 {
public static void main(String... args) {
List<Number> nombres = new ArrayList<Number>();
ArrayList<Integer> entiers = new ArrayList<Integer>();
ArrayList<Long> entierlongs = new ArrayList<Long>();
ArrayList<Float> flottants = new ArrayList<Float>();
//ArrayList<String> string = new ArrayList<String>();
entiers.add(5);entiers.add(55555555);entiers.add(Integer.MAX_VALUE);
entierlongs.add(Long.MAX_VALUE);
flottants.add(14.5F);flottants.add(65.5F);
// Nous pouvons ajouter n'importe quel type issu de Collection
// grace au wilcard dans : boolean addAll(Collection<? extends E> c)
nombres.addAll(entiers);
nombres.addAll(entierlongs);
nombres.addAll(flottants);
//nombres.addAll(string); // provoque une erreur, String n'est pas une collection
for (Number n:nombres) System.out.println(n);
}
}
Types paramétrés avec wildcard non bornés (Unbounded wildcard parameterized type)
Pour définir une méthode générique qui affiche une liste d'objets
quelconque, ce n’est pas évident sans générique.
Voir Exemple SansWilCard.java
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
public class SansWilCard {
public static void afficher(List<Object> liste) {
for (Object element : liste) {
System.out.print(element + " ");
}
System.out.println();
}
public static void main(String... args) {
List<Integer> entiers = Arrays.asList(1, 2, 3);
List<String> chaines = Arrays.asList("A", "B", "C");
// Erreurs de Compilation
afficher(entiers);
afficher(chaines);
}
}
Pour définir une méthode générique qui affiche une liste d'objets quelconque, ce n’est pas évident sans générique.
Voir Exemple SansWilCard.java
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
public class SansWilCard {
public static void afficher(List<Object> liste) {
for (Object element : liste) {
System.out.print(element + " ");
}
System.out.println();
}
public static void main(String... args) {
List<Integer> entiers = Arrays.asList(1, 2, 3);
List<String> chaines = Arrays.asList("A", "B", "C");
// Erreurs de Compilation
afficher(entiers);
afficher(chaines);
}
}
References:
- P.O.O. 2(Programmation Orientée Objet 2) - CHOUITI Sidi Mohammed - Cours pour 2ème année Ingénieurs - Département d’Informatique - Université de Tlemcen - 2023-2024
Created:: 2024-03-11 12:03