Les generics en java

Ce billet tente de répondre, avec des exemples concrets, aux questions suivantes :

  • A quoi sert les generics ?
  • Comment écrire une classe générique "generic class"?
  • Comment déclarer une variable ou un attribut générique?
  • Comment écrire une méthode générique "generic method"?

Les génériques permettent tout simplement d'écrire un code qui utilise un type (paramètre) abstrait.
Par conséquent, ce code est applicable de façon "type-safe" à une large gamme de types (objets).
Ce code est concis, réutilisable, type-safe et facilement testable.

Pour mieux comprendre les génériques, il suffit de pratiquer pour voir ensemble combien il est utile de les connaître.

Un code type-safe c'est un code qui s'exécute sans aucune erreur (runtime) de typage car c'est le compilateur qui garantit la cohérence du type utilisé dans les génériques.
Vous notez qu'avec les génériques, on transfère la responsabilité des types du développeur au compilateur.

Les génériques font leur apparition en java 5.
Commençons par évoquer les motivations d'introduction des "generics".

MOTIVATIONS

Le code (legacy code) des versions avant java 5 regorge d'exemples de ce style:


        List liste = new ArrayList();
	liste.add(new Chat());
	liste.add(new Person("Mr"));
	liste.add(new Camion());
	Iterator iter = liste.iterator();
	 while (iter.hasNext()) {
		Object o = iter.next();
	  if (o instanceof Chat) {
				// ...
	  }
			if (o instanceof Person) {
				// ......
	 }
			// //etc
	}

Nous voyons que le conteneur List (sans typage) peut contenir tout et n'importe quoi. C'est un fourre-tout car en réalité c'est List<Object>.
Toute classe java est un Object forcément.
Ainsi dans ce code c'est au développeur de veillez à ce qu'il insère dans les conteneurs de java (ici List).

On peut relever que des kilomètres de code sont présents rien que pour "caster" les objets et/ou éviter des exceptions de typage en runtime.

Avec les génériques, les choses se feront plus proprement:

     //avec generics
  List<Person> listeg = new ArrayList<Person>();
  // ERR COMPILE listeg.add( new Chat());
  listeg.add(new Person("Me"));
  // ERR COMPILE listeg.add(new Camion());
  for (Person p : listeg) {
	System.out.println(p);
  }

Notez qu'un type paramétré est l'instance d'un type générique avec des valeurs concrètes pour l'ensemble des paramètres.

DECLARER UNE CLASSE GENERIQUE

Un classe générique est déclarée avec un ou plusieurs paramètres abstraits.
La convention encourage à désigner le paramètre abstrait avec une lettre majuscule.
Mais ceci n'est pas obligatoire.
Depuis la version 5, l'API Java contient beaucoup de classes (interfaces) génériques.

Exemple,

     interface List<E> extends Collection<E>

Il est important de constater que, contrairement aux templates en C++, un seul fichier de classe correspond à cette déclaration, et ce indépendamment du nombre d'instances implémentant cette interface (avec, éventuellement, une valeur différente du paramètre E pour chaque implémentation).

La déclaration d'une classe générique se fait ainsi:
Un exemple fort utile c'est la couche (de plus en plus mince) DAO où le besoin d'écrire du générique est manifeste.
Voici un exemple:

abstract class  AbstractGenericDaoImpl<T, ID extends Serializable> {
    public ID save(T entite) {
        //...
    }
    public void update(T entite){
	//...
    }
}

Ci-après d'autres exemples de déclarations de classes génériques en allant du simple au complexe (mais non compliqué):

class Dao<T> {...}

Donc c'est une classe nommée Dao avec un seul paramètre nommé T. Aucune contrainte n'est imposée au paramètre T.

 class Service<T,S> {...}

Cette classe Service a deux paramètres, T and S.

 class Dao<T extends Serializable> {...}

La classe Dao avec un seul paramètre T qui est contraint d'étendre Serializable.

class Service<T extends Serializable & Comparable, S extends T> {...}

C'est une classe Service avec deux paramètres T et S.

A l'instantiation de Service, le paramètre S doit être une sous classe du paramètre T.
Le paramètre T doit implémenter les deux interfaces Serializable et Comparable.

DECLARATION DE VARIABLE OU ATTRIBUT GENERIQUE

Une lourdeur lors de la déclaration d'une variable générique, est le fait de devoir paramétrer le type de la variable (qui est généralement une interface) et de paramétrer l'instance dans la même ligne.
Un exemple éclaircira mieux mes propos:

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

Heureusement en java 7, ces déclarations vont être allégées avec l'inférence de type.
D'autres exemples un peu sophistiqués:

 List<? extends Person> listePers = new ArrayList<Person>();

 List<? super Person> lPers=new ArrayList<Person>();

DECLARER UNE METHODE GENERIQUE

L'API java5+ regorge de méthodes génériques et elles envahissent nos projets pour un code concis de meilleure qualité.

Les déclarations de méthode générique sont simples, en voici quelques unes:

 public <T> void afficher(List<T> liste);            //1
 static <F> F save(E f);                                     //2
 private  void update(List<? extends E> liste)  //3

A la ligne 1, nous précisons que la méthode ne retourne rien (void) mais prend un paramètre abstrait nommé T. Aucune contrainte sur T.

A la ligne 2 la méthode save prend en argument un type F et retourne un type F.
A la ligne 3 la méthode prend en argument un sous-type de E. Le type abstrait E est déjà déclaré dans la classe générique.

CONCLUSION
En conclusion, il faudrait lors du refactoring d'un code existant, identifier les pièges et les éviter.
Le prochain billet reviendra justement sur ces pièges ainsi que sur les notions Lower & Upper bound, "erasure" et l'héritage.
Toutes ces notions sont fort utiles pour une utilisation un peu avancée des génériques, pour comprendre les restrictions sur leur emploi et enfin pour bien déchiffrer les warnings/errors d'Eclipse ou de tout autre IDE.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.