Principe

Ce concept permet de créer des structures de données (classes) et des fonctions/méthodes capables de travailler avec des types génériques au lieu de types spécifiques prédéterminés.

Le code générique peut fonctionner avec n’importe quel type de données (entiers, flottants, objets, etc.) sans être réécrit pour chaque type. Dans le code, certains types ne sont pas spécifiés à l’avance, mais seront déterminés à l’exécution.

Les paramètres de type permettent de définir une sorte de moule ou de modèle (template) qui pourra ensuite être utilisant avec différents types réels.

Déclaration

Pour déclarer une classe générique :

class NomDeClasse<liste types generiques>

La liste des types génériques contient les noms des types (classes), qui sont en général notés pr des lettres majuscules.

Exemples :

class Class1<T> { .. }
class Class2<X, Y, Z> { ... }

En pratique, la liste ne comprend le plus souvent qu’un seul type générique.

Types autorisés

Attention

En Java, contrairement au C++, les types génériques sont forcément des classes et pas des types fondamentaux

Les types déclarés dans l’entête d’une classe générique peuvent être utilisés pour déclarer des attributs, des paramètres ou des valeurs de retour de méthodes.

Exemple d’implémentation

class MaClasse<T,U,V> {
	T attr1;
	U attr2;
	double attr3;
 
	public MaClasse(T t, U u, double d) {
		attr1 = t; attr2 = u; attr3 = d;
	}
 
	V maMethode(T t) {
		V val;
		if (t == attr1) { ... } // -> problème éventuel
		...
		return val;
	}
}

Comparaison t == attr1

Ce n’est pas forcément le meilleur moyen de comparer. Nous pouvons utiliser la méthode .equals mais on ne sait pas si elle est implémentée dans T

Méthodes d’instanciations

Instanciation classique

Dans ce cas, on va déclarer le type, tel que, par exemple :

ArrayList<String> l = new ArrayList<String>();

Instanciation raccourcie

On peut également dire au compilateur de prendre directement les types :

ArrayList<String> l = new ArrayList<>();

Exemples

MaClasse<Integer, String, Date> m;
Integer i = new Integer(5);
String  s = "Bonjour";
m = new MaClasse<Integer, String, Date>(i, s, 1.2); // instanciation, syntaxe classique
m = new MaClasse<>(i, s, 1.2); // Instanciation, syntaxe raccourcie
MaClasse<int, String, Date> m; // erreur de compilation
 
MaClasse<Integer, String, Date> m; 
 
Date d;
d = m.maMethode(new Integer(6)); // OK
d = m.maMethode(new Calendar()); // Erreur de compilation
 
float f = m.maMethode(new Integer(10)); // Erreur de compilation

Utiliser des classes génériques

Pour les classes englobantes des types fondamentaux, le compilateur peut faire le transtypage automatiquement.

MaClasse<Integer, String, Boolean> m;
 
m = new MaClasse<>(5, "Bonjour", 1.2);
boolean b = new m.maMethode(3);

Utilisation des opérateurs arithmétiques

Les opérateurs tels que +, -, *, /, % et tous les opérateurs logiques (>, <, ==) sont à EVITER sauf si c’est spécifiquement déclaré.

On préfèrera utiliser des interfaces telles que Comparable pour obtenir des méthodes utilisables de façon sécurisée (ex. .equals(), .isLess(), compareTo(), …)

Exemple

On peut par exemple implémenter la structure de données Node:

class Node<T> {
	private List<Node<T>> children;
 
	private T data;
 
	public Node(T data){
		this.data = data;
		children = new ArrayList<Node<T>>();
	}
 
	public T getData(){ return data; }
	public void setData(T data) { this.data = data; }
 
	public void addChildren(Node<T> child){
		children.addChild(child)
	}
 
	public Node<T> createChild(T data){
		Node<T> n = new Node<T>(data);
 
		this.addChildren(n);
		return n;
	}
}
class Tree<T> {
	private Node<T> root;
 
	public Tree() { root = null; }
 
	public Node<T> createNode(T data, Node<T> parent){
		Node<T> n;
		
		if (parent == null){
			n = new Node<T>(data);
			if (root != null) {
				n.addChildren(root)
			}
			root = n;
		} else {
			n = parent.addChildren(data);
		}
		
		return n;
	}
}