Programmation par objets en Java (GLIN 505)

Ce document présente des exercices pratiques sur la programmation par objets en Java, centrés sur le polymorphisme paramétrique, ou généricité. Il s'adresse aux étudiants en informatique souhaitant maîtriser la conception de classes génériques, l'implémentation d'interfaces génériques, ainsi que les contraintes de types dans les classes génériques.

D'après le document Programmation par objets en Java (GLIN 505)

Cet article a été rédigé automatiquement à partir du document source, puis vérifié avant publication.

Document source

Programmation par objets en Java (GLIN 505)

Programming, Java, Generics, Data Structures · PDF · 4 pages · 2012

Afficher l'aperçu du document

Consulter le document original →

Ce document présente des exercices pratiques sur la programmation par objets en Java, centrés sur le polymorphisme paramétrique, ou généricité. Il s'adresse aux étudiants en informatique souhaitant maîtriser la conception de classes génériques, l'implémentation d'interfaces génériques, ainsi que les contraintes de types dans les classes génériques.

Polymorphisme paramétrique : classe générique implémentant une interface générique

On considère une interface générique IPile<A> définissant une pile avec les opérations classiques :

public interface IPile<A> {
    boolean estVide();
    void empile(A a);
    A depile(); // retourne l'élément en sommet de pile et dépile
    int nbElements();
    A sommet(); // retourne le sommet de pile mais ne le dépile pas
}

Le premier exercice consiste à écrire une classe générique CPile<A> qui implémente cette interface. Les éléments de la pile seront stockés dans une liste chaînée, par exemple une instance de java.util.LinkedList<A>.

Si les interfaces ne sont pas encore maîtrisées, on peut remplacer IPile par une classe abstraite APile dont toutes les méthodes sont abstraites et publiques, et faire hériter CPile de cette classe abstraite.

Un petit programme de test doit ensuite être écrit, instanciant des piles de différents types, par exemple CPile<String> ou CPile<Integer>, et manipulant ces piles.

Exemple minimal d'utilisation

CPile<String> pileDeStrings = new CPile<>();
pileDeStrings.empile("Bonjour");
pileDeStrings.empile("Monde");
System.out.println(pileDeStrings.sommet()); // Affiche "Monde"
System.out.println(pileDeStrings.depile()); // Retire et affiche "Monde"
System.out.println(pileDeStrings.nbElements()); // Affiche 1

Classe générique pour le tri d'un tableau

La classe suivante encapsule un tableau d'entiers et propose une méthode de tri par bulles ainsi qu'une méthode d'affichage :

public class Tableau {
    private int T[];

    public Tableau(int T[]) {
        this.T = T; // recopie superficielle
    }

    public void triBulles() {
        int i = T.length - 2;
        boolean ech = true;
        while (i >= 2 && ech) {
            ech = false;
            for (int j = 0; j <= i; j++) {
                if (T[j] > T[j + 1]) {
                    int aux = T[j];
                    T[j] = T[j + 1];
                    T[j + 1] = aux;
                    ech = true;
                }
            }
            i--;
        }
    }

    public void affiche() {
        for (int i = 0; i < T.length; i++)
            System.out.print(T[i] + " ");
        System.out.println();
    }

    public static void main(String[] args) {
        int T[] = {10, 2, 6, 11, 7, 2, -1, 0, 9};
        Tableau obj = new Tableau(T);
        obj.triBulles();
        obj.affiche();
    }
}

Le but est de généraliser cette classe pour trier un tableau d'objets de n'importe quel type, en utilisant la généricité. La solution consiste à rendre la classe générique et à contraindre le type à être comparable, afin de pouvoir comparer les éléments entre eux dans la méthode de tri.

Idée de solution générique

public class Tableau<T extends Comparable<T>> {
    private T[] T;

    public Tableau(T[] T) {
        this.T = T;
    }

    public void triBulles() {
        int i = T.length - 2;
        boolean ech = true;
        while (i >= 2 && ech) {
            ech = false;
            for (int j = 0; j <= i; j++) {
                if (T[j].compareTo(T[j + 1]) > 0) {
                    T aux = T[j];
                    T[j] = T[j + 1];
                    T[j + 1] = aux;
                    ech = true;
                }
            }
            i--;
        }
    }

    public void affiche() {
        for (int i = 0; i < T.length; i++)
            System.out.print(T[i] + " ");
        System.out.println();
    }
}

Files d'attente génériques et files d'attente avec priorité

File d'attente simple

Une classe FileAttente est donnée, stockant des objets de type Personne dans un ArrayList<Personne> :

public class FileAttente {
    protected ArrayList<Personne> contenu;

    public FileAttente() {
        contenu = new ArrayList<>();
    }

    public void entre(Personne p) {
        contenu.add(p);
    }

    public Personne sort() {
        Personne p = null;
        if (!contenu.isEmpty()) {
            p = contenu.get(contenu.size() - 1);
            contenu.remove(contenu.size() - 1);
        }
        return p;
    }

    public boolean estVide() {
        return contenu.isEmpty();
    }

    public String toString() {
        return "" + contenu;
    }
}

Il est demandé de proposer une classe générique représentant une file d'attente contenant des objets de n'importe quel type, par exemple des personnes, des voitures, etc.

File d'attente avec priorité

On considère l'interface suivante :

public interface ElementAvecPriorite {
    int priorite();
}

La tâche est d'écrire une classe générique représentant une file d'attente avec priorité, contenant des objets de n'importe quel type à condition qu'ils implémentent ElementAvecPriorite. Les éléments sortent de la file en favorisant ceux qui ont la plus petite priorité.

Modification de la classe Personne

La classe Personne est définie ainsi :

public class Personne {
    private String nom;
    private int age;

    public Personne() {}

    public Personne(String n, int a) {
        nom = n;
        age = a;
    }

    public String getNom() {
        return nom;
    }

    public void setNom(String n) {
        nom = n;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int a) {
        age = a;
    }

    public String toString() {
        return nom + " " + age;
    }
}

Pour pouvoir stocker des personnes dans une file d'attente avec priorité, il faut modifier la classe Personne pour qu'elle implémente ElementAvecPriorite. La priorité est déterminée à partir de l'âge selon trois niveaux :

  • Priorité 1 : âge entre 0 et 12
  • Priorité 2 : âge de 60 ans et plus
  • Priorité 3 : autres âges

Il suffit d'ajouter la méthode suivante :

@Override
public int priorite() {
    if (age >= 0 && age <= 12) return 1;
    else if (age >= 60) return 2;
    else return 3;
}

Exemple d'utilisation

FileAttenteAvecPriorite<Personne> file = new FileAttenteAvecPriorite<>();
file.entre(new Personne("Alice", 10));
file.entre(new Personne("Bob", 65));
file.entre(new Personne("Charlie", 30));

while (!file.estVide()) {
    System.out.println(file.sort());
}

Les personnes seront sorties de la file selon leur priorité (1 puis 2 puis 3).

Paramétrage contraint : couples d'animaux

Définition des interfaces et classes de base

On définit deux interfaces vides :

public interface Mâle {}
public interface Femelle {}

Des classes d'animaux mâles et femelles implémentent ces interfaces, par exemple :

public class Taureau implements Mâle {
    public String toString() { return "Taureau"; }
}

public class Vache implements Femelle {
    public String toString() { return "Vache"; }
}

public class Dauphin implements Mâle {
    public String toString() { return "Dauphin"; }
}

public class Dauphine implements Femelle {
    public String toString() { return "Dauphine"; }
}

Classe générique CoupleConventionnel

En dérivant la classe Paire vue en cours, on définit une classe générique CoupleConventionnel<M extends Mâle, F extends Femelle> représentant un couple constitué d'un mâle et d'une femelle.

On peut ainsi créer des couples d'espèces différentes, par exemple un dauphin et une vache :

CoupleConventionnel<Dauphin, Vache> coupleMixte = new CoupleConventionnel<>(new Dauphin(), new Vache());

Classe générique CoupleEspèce

On dérive également une classe générique CoupleEspèce<T> représentant un couple constitué de deux membres de la même espèce, sans contrainte sur le sexe. La notion de même espèce est représentée par le même type générique T.

On ne peut plus créer des couples d'espèces différentes :

CoupleEspèce<Dauphin> coupleDauphins = new CoupleEspèce<>(new Dauphin(), new Dauphin());
// Impossible : CoupleEspèce<Dauphin, Vache> coupleMixte = ...

Classe générique CoupleFertile

Enfin, on propose une classe CoupleFertile<T extends Mâle && Femelle> représentant un couple constitué d'un mâle et d'une femelle de la même espèce. Cette classe impose que les deux membres soient de sexes opposés et de la même espèce.

On ne peut plus créer de couples de deux mâles ou deux femelles, ni de couples d'espèces différentes, même si les sexes sont opposés.

Annexe : la classe LinkedList<E>

La classe java.util.LinkedList<E> est une liste chaînée générique. Voici quelques méthodes publiques importantes :

  • LinkedList() : construit une liste vide.
  • void addFirst(E o) : insère un élément en tête de liste.
  • E element() : récupère sans retirer le premier élément (lève une exception si la liste est vide).
  • E getFirst() : retourne le premier élément (lève une exception si la liste est vide).
  • E peek() : récupère sans retirer le premier élément, retourne null si la liste est vide.
  • E removeFirst() : retire et retourne le premier élément (lève une exception si la liste est vide).
  • int size() : retourne le nombre d'éléments dans la liste.

Glossaire des termes clés

  • Polymorphisme paramétrique (généricité) : mécanisme permettant de définir des classes ou interfaces avec des paramètres de type, pour écrire du code réutilisable avec différents types.
  • Interface générique : interface définissant des méthodes avec des types paramétrés, par exemple IPile<A>.
  • Classe générique : classe définie avec un ou plusieurs paramètres de type, par exemple CPile<A>.
  • Liste chaînée (LinkedList) : structure de données composée d'éléments liés les uns aux autres, permettant insertion et suppression efficaces en tête ou en queue.
  • File d'attente (Queue) : structure de données où les éléments sont insérés à la fin et retirés au début (FIFO).
  • File d'attente avec priorité : file où les éléments sont retirés selon une priorité définie, pas nécessairement dans l'ordre d'insertion.
  • Contraintes de type : restrictions imposées aux paramètres génériques, par exemple T extends Comparable<T> pour garantir que les éléments sont comparables.
  • Interface vide : interface sans méthodes, utilisée pour marquer ou catégoriser des classes (ex. Mâle et Femelle).
  • Classe abstraite : classe qui peut contenir des méthodes abstraites (non implémentées) et sert de base à d'autres classes.

Points clés à retenir

  • La généricité permet d'écrire des classes et interfaces réutilisables avec différents types, améliorant la sécurité et la clarté du code.
  • Une classe générique peut implémenter une interface générique, en respectant les mêmes paramètres de type.
  • Pour trier des tableaux génériques, il faut contraindre le type à implémenter Comparable afin de pouvoir comparer les éléments.
  • Les files d'attente génériques facilitent la gestion d'éléments de différents types, avec ou sans priorité.
  • Les contraintes sur les paramètres génériques permettent de modéliser des relations complexes, comme des couples d'animaux avec contraintes de sexe et d'espèce.
  • Les interfaces vides peuvent servir à marquer des classes pour imposer des contraintes de type dans les classes génériques.

Partager

Commentaires

Aucun commentaire pour le moment. Posez la première question.

Les commentaires sont relus avant publication. Votre e-mail n'est jamais affiché.

← Toutes les révisions