Les génériques (generics)

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é.

Problèmes

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

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

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

	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 et l'API Collection.

	List<String> liste = new ArrayList<String>();  

L’idée derrière les génériques

ArrayList<T> 

Vocabulaire s utilisés pour les génériques

ArrayList<T> 
ArrayList<T> 
ArrayList<Long> 
ArrayList<Long> 
ArrayList() 

L'utilisation de types génériques

Les génériques peuvent être utilisés avec :

// 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 :

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 :

	new ArrayList<?> `
	 interface MaList extends List<?>

Exemples

Set <?> : pour un ensemble qui peut contenir tout type d'objet.
Cependant, à l'exécution, cet ensemble devra être assignée à un type concret.
·
List<?extends Number> : pour une liste qui peut contenir des instances de type Number ou sous-type de Number.
·
· Comparator<? super Integer> : pour une comparateur d'objets de type Integer ou d'un de ses super-types.

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:

Created:: 2024-03-11 12:03