Rechercher
 

Améliorations / Corrections

Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.

Emplacement :

Description des améliorations :

Introduction à la généricité en Java

Aspects complémentaires liés aux exceptions Apprendre à coder une classe générique



Accès rapide :
La vidéo
Qu'est-ce que la généricité ?
Utilisation de types génériques du Java SE
La syntaxe traditionnelle
L'opérateur diamant
Quelques types génériques proposés par le Java SE
Les collections Java
Le moteur de réflexion Java

La vidéo

Cette vidéo vous présente le concept de généricité et de classe générique. Quelques classes, proposées par l'API du Java SE, sont utilisées pour appuyer la discussion.


Introduction à la généricité en Java

Qu'est-ce que la généricité ?

En Programmation Orientée Object (POO), la généricité est un concept permettant de définir des algorihtmes (types de données et méthodes) identiques qui peuvent être utilisés sur de multiples types de données. Cela permet donc de réduire les quantités de codes à produire.

Peut-être vous dites-vous qu'on peut traiter ce type problème via du polymorphisme : effectivement, c'est une possibilité. Mais la généricité permettra d'avoir un code plus fortement typé et donc plus sûr. Pour s'en convaincre, analysons le code suivant : il crée une collection de type java.util.ArrayList pour y stocker un ensemble de chaînes de caractères. Par défaut, ce type de collection manipule des instances de type Object. Donc, par polymorphisme, on peut y stocker n'importe quoi, y compris des chaînes de caractères. Le problème, c'est que rien ne nous'interdit d'y stocker des dates (car la classe java.util.Date dérive d'Object) ou n'importe quoi d'autre.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
import java.util.ArrayList;
import java.util.Date;

public class Demo {

    public static void main( String [] args ) {
   
        // Cette ligne produit un warning, mais pour le moment on l'ignore
        ArrayList collection = new ArrayList();
        
        // On y stocke nos chaînes de caractères
        collection.add( "Essai" );
        collection.add( "Cours Java" );
        collection.add( "Tutoriel Java" );
            
        // Mais rien nous interdit d'y mettre autre chose
        collection.add( new Date() );
    
        // Maintenant on parcourt la collection
        for ( Object value : collection ) {
            System.out.println( value );
        }
        
    }

}
Exemple d'une collection utilisant le polymorphisme pour choisir le type de données à y stocker
la ligne permettant de définir la collection produit un warning. En fait, c'est normal, car les collections Java sont très souvent implémentées de manière générique. Or nous n'utilisons pas encore cet aspect et par défaut c'est le polymorphisme qui fait le boulot. Cela n'étant pas l'approche la plus optimale, un warning est produit. D'autres warnings sont aussi produits lors de l'utilisation de la collection).

Si nous utilisons la généricité, nous pouvons indiquer que la collection ne peut contenir que des chaînes de caractères. Si l'on cherche à contourner le typage, une erreur de compilation sera produite à la place des warnings précédents. Votre programme sera donc plus fortement typé et plus sûr. Voici un premier exemple d'utilisation de la généricité.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
import java.util.ArrayList;

public class Demo {

    public static void main( String [] args ) {
   
        // On crée une collection de chaînes de caractères
        ArrayList<String> collection = new ArrayList<String>();
        
        // On y stocke nos chaînes de caractères
        collection.add( "Essai" );
        collection.add( "Cours Java" );
        collection.add( "Tutoriel Java" );
            
        // Cette ligne ne compile plus
        //collection.add( new Date() );
    
        // Maintenant on parcourt la collection : 
        // elle contient des instances de type String.
        for ( String value : collection ) {
            System.out.println( value );
        }
        
    }

}
Exemple d'utilisation d'une collection générique
la généricité est mise en oeuvre en ligne 8 lors de la construction de la collection.

Utilisation de types génériques du Java SE

Le Java SE propose un très grand nombre de classes génériques dans sa librairie et notamment les classes de collections. Les différentes classes de collections Java partagent certaines caractéristiques communes : elles implémentent donc des interfaces communes. Ces interfaces sont elles-mêmes génériques, comme nous allons le voir.

La syntaxe, en termes d'utilisation de types génériques, a évolué au cours du temps. D'ailleurs, les premières versions de Java ne supportaient pas la généricité : on utilisait donc à l'époque le polymorphisme. Le concept de généricité a été introduit dans le langage Java à partir de sa version 5.0. La version Java SE 7.0 a légèrement simplifié les choses avec l'apparition de l'opérateur diamant (diamond operator).

La syntaxe traditionnelle

L'exemple proposé précédent utilise la syntaxe traditionnelle. L'information de généricité doit être mentionnée sur la déclaration de la variable, mais aussi lors de son instanciation (lors de l'appel de l'opérateur new). Pour rappel, voici la ligne de construction de la collection de type java.util.ArrayList.

 1 
 2 
// On crée une collection de chaînes de caractères
ArrayList<String> collection = new ArrayList<String>();
Syntaxe traditionnelle pour une création d'instance basée sur un type générique

L'opérateur diamant

Depuis le Java SE 7.0, la syntaxe utilisée pour produire une instance basée sur un type générique a été simplifiée grâce à l'opérateur diamant. Pour faire simple, vous avez déjà introduit le type de généricité lors de la déclaration de la variable, vous pouvez donc demander au compilateur, via l'opérateur diamant, de déduire le type de données à utiliser lors du new à partir de la définition de la variable. Cette déduction du type à utiliser est aussi appelée inférence de type. Voici un exemple d'utilisation de l'opérateur diamant (<>).

 1 
 2 
// On créé une collection de chaînes de caractères
ArrayList<String> collection = new ArrayList<>();
Utilisation de l'opérateur diamant pour une création d'instance basée sur un type générique

L'inférence de type (la déduction du type) peut aussi avoir lieu sur une instruction placée après la déclaration de la variable. En voici un exemple.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
// On déclare la variable basée sur un type générique
ArrayList<String> collection = null;

// On imagine quelques lignes de code

// On crée maintenant l'instance en utilisant l'opérateur diamant
collection = new ArrayList<>();
Utilisation de l'opérateur diamant pour une création d'instance basée sur un type générique

Quelques types génériques proposés par le Java SE

On retrouve la généricité à différents endroits dans la librairie Java SE. En voici quelques exemples.

Les collections Java

Beaucoup de collections Java implémentent l'interface générique java.util.Collection. Cette interface est définie ainsi (nous ne listerons par l'intégralité de ses méthodes, je vous renvoie sur la Javadoc à ce sujet).

 1 
 2 
 3 
 4 
 5 
public interface Collection<E> extends Iterable<E> {

    // Spécification des méthodes de l'interface

}
L'interface générique java.util.Collection
l'interface Collection implémente aussi l'interface générique java.util.Iterable.

Deux interfaces majeures étendent l'interface Collection : java.util.List et java.util.Set. La première interface permet de représenter une ensemble d'éléments séquentiel : on y accède via un index de positionnement (à partir de zéro, bien entendu). La seconde représente des collections à valeur unique (vous ne pouvez pas stocker deux fois la même valeur dans un Set, contrairement à une List).

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
public interface List<E> extends Collection<E> {
    
    // Spécification des méthodes de l'interface

}


public interface Set<E> extends Collection<E> {

    // Spécification des méthodes de l'interface

}
Les interfaces génériques java.util.List et java.util.Set

Une autre interface importante, pour les collections Java, est l'interface java.util.Map qui représente une collection associative clé-valeur. Voici la définition de l'interface Map.

 1 
 2 
 3 
 4 
 5 
public interface Map<Key, Value> { 
            
    // Spécification des méthodes de l'interface

}
L'interface générique java.util.Map

Mais pourquoi, les diverses collections Java sont basées sur des interfaces ? La raison en est simple : pour une même catégorie de collections, il peut exister plusieurs algorithmes. Par exemple, on peut choisir d'implémenter une collection d'éléments qui se suivent en séquence, soit par une liste chaînée, soit par un tableau (il y aurait même encore d'autres possibilités). Le fait de typer la collection par interface impose une API partagée par plusieurs implémentations et vous aurez toute latitude de changer la classe d'implémentation, si celle-ci n'est plus adaptée. La généricité et l'opérateur diamant fonctionnent parfaitement dans ce cas. En voici un exemple.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
import java.util.ArrayList;
import java.util.List;


public class Demo {

    public static void main( String [] args ) {
   
        // On crée une collection de chaînes de caractères
        List<String> collection = new ArrayList<String>();
        
        // On y stocke nos chaînes de caractères
        collection.add( "Essai" );
        collection.add( "Cours Java" );
        collection.add( "Tutoriel Java" );
            
        // Maintenant on parcourt la collection : 
        // elle contient des instances de type String.
        for ( String value : collection ) {
            System.out.println( value );
        }
        
    }

}
Exemple d'utilisation d'une collection générique

Et voici un autre exemple appliqué à l'utilisation d'une table associative (clé/valeur).

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
import java.util.HashMable;
import java.util.Map;


public class Demo {

    public static void main( String [] args ) {
   
        Map<String, Person> agenda = new HashMap<>();
        agenda.put( "007", new Person( 7, "James", "Bond", "bond@mi6.uk" ) );
        agenda.put( "000", new Person( 0, "Johnny", "English", "bean@mi6.uk" ) );
        
        Person person = agenda.get( "007" );
        System.out.println( person );
        
    }

}
Exemple d'utilisation d'une collection générique associative

Le moteur de réflexion Java

La réflexion est un concept de Programmation Orientée Objet qui permet d'obtenir des informations structurelles (des méta-données) sur un type de données. Le langage Java supporte la réflexion au travers de son API java.util.refect. On peut retrouver des méta-données sur tout objet Java ou toute classe. Ces données seront stockées dans une instance de type java.lang.Class et pourront être manipulées via l'API de réflexion.

Et la généricité la dedans, me direz-vous. Et bien, la classe java.lang.Class est générique, car elle porte des informations descriptives pour n'importe quel type Java. Voici un exemple d'utilisation qui liste l'ensemble des attributs définis directement sur la classe Demo.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
import java.lang.reflect.Field;
import java.util.Date;


public class Demo {

    private String name;
    private int counter;
    private double price;
    private Date birthday;
    
    public static void main( String [] args ) throws Exception {

        // On accède aux méta-données descriptives du type Demo
        Class<Demo> metadata = Demo.class;  
        
        // On récupère la liste des attributs directement définis dans Demo
        Field [] attributes = metadata.getDeclaredFields();
        
        // On parcourt ces attributs et on les affiche
        for( Field attribute : attributes ) {
            System.out.printf( "%s of type %s - %b\n",
                    attribute.getName(),
                    attribute.getType().getName(),
                    attribute.getType().isPrimitive()
            );
        }
        
    }

}
Exemple d'utilisation de la généricité avec l'API de réflexion

Et voici les résultats produits par cet exemple.

name of type java.lang.String - false
counter of type int - true
price of type double - true
birthday of type java.util.Date - false
nous présenterons l'API de réflexion Java plus en détails dans de prochains chapitres.


Aspects complémentaires liés aux exceptions Apprendre à coder une classe génériques