Université de Nice-Sophia Antipolis Algorithmique & Programmation Deug MIAS-MI 1 2002–2003 TP N 13 Les types abstraits, les piles, les files Buts : – montrer comment la notion d’interface en Java permet d’implémenter celle de type abstrait vue en cours. – montrer que les objets de Java ressemblent à ce qu’en Cours et TD nous appelons des pointeurs sur des articles 1 – réaliser des implémentations des types abstraits pile et file. 1 Une représentation des piles Vous trouverez dans les ressources une collection de fichiers que vous devrez examiner avec le plus grand soin car ils sont destinés à vous servir de modèles pour la suite. – Stack.java déclare une interface de Java : une interface ressemble à une classe, mais tous les attributs y sont forcément static et final, il n’y a aucun constructeur et les méthodes sont toutes abstraites (elles n’ont pas de corps). En fait, il ne s’agit que de l’énumération des méthodes que doit nécessairement posséder une classe pour avoir le droit de mettre dans son en-tête implements Stack et pour que ses instances aient donc le droit d’être déclarées Stack. – StackA.java déclare une classe qui implémente l’interface Stack avec le triplet d’une capacité, d’un tableau de cette capacité et d’un indice dans ce tableau. Cela correspond presque exactement à ce que, dans le Cours et les TD, on aurait probablement déclaré : type T_contenu = ? T_descripteur_de_pile = article tailleMax : entier data : tableau 1 .. tailleMax de T_contenu sommet : 0 .. tailleMax T_pile = pointeur sur T_descripteur_de_pile finarticle – StackB.java déclare une classe qui implémente l’interface Stack avec un chaînage simple. La classe Cell qui implémente les cellules du chaînage a été déclarée à l’intérieur de la classe StackB ce qui facilite les choses en permettant que ses attributs, même déclarés private, soient visibles n’importe où dans StackB. Une telle classe s’appele une classe emphmembre (elle est un membre, comme les attributs et les méthodes, de sa classe englobante). Dans le Cours ou les TD, on aurait probablement écrit : type T_contenu = ??? T_descripteur_de_cellule = article contenu : T_contenu suivant : T_cellule finarticle 1. Il s’agit d’une ressemblance particulièrement intéressante à relever, mais ils ne faut cependant pas croire que les objets sont exactement des pointeurs sur des articles. 1 T_cellule = pointeur sur T_descripteur_de_cellule T_descripteur_de_pile = article tête : T_cellule finarticle T_pile = pointeur sur T_descripteur_de_pile – Les classes EmptyStackException.java et FullStackException.java peuvent être négligées à la première lecture. Ces fichiers déclarent des classes qui étendent RuntimeException (exceptions qui peuvent survenir lors de l’exécution) afin d’avoir des exceptions sur mesure. Vous remarquerez que, si l’exception de pile vide ne fait pas grand chose, l’exception de pile pleine stocke la taille maximale dans un attribut afin de pouvoir le renvoyer sur demande. – Stacks.Java décrit une classe exécutable (qui contient une méthode main). Elle teste les performances comparées des deux implémentations précédentes. Vous remarquerez que la pile y est toujours déclarée Stack : ce n’est qu’au moment de l’instanciation qu’on choisit telle ou telle implémentation en choisissant tel ou tel constructeur ! Exercice 1 Allez consuler la documentation de la classe classe Stack de l’API Java et proposez une troisième implémentation de notre interface Stack qui commencerait 2 : public class StackC implements Stack { // Attribut private java.util.Stack stack ; // Constructeur public StackC () { stack = new java.util.Stack() ; } } Comparez ses performances avec celles des deux implémentations précédentes. Interprétez les résultats. 2 Une représentation des files d’attente Sur le modèle de la section précédente, vous allez traiter le problèmes des files d’attente en écrivant les quatre fichiers Queue.java, QueueA.java, QueueB.java et Queues.java. Exercice 2 Définir l’interface Queue, proposant les trois méthodes abstraites isEmpty, enqueue et dequeue. Puis implémentez les files d’attente QueueA en utilisant un tableau circulaire. Exercice 3 Implémentez les files d’attente QueueB en utilisant une liste chaînée circulaire avec sentinelle. L’attribut, le constructeur et la classe des cellules vous sont donnés : public class QueueB implements Queue { private Cell sentry ; public QueueB () { sentry = new Cell(null) ; sentry.next = sentry ; sentry.previous = sentry ; } class Cell { private Object content ; private Cell next, previous ; Cell (Object o) { content = o ; next = previous = null ; 2. On n’utilise pas import mais le chemin explicite, afin d’éviter les conflits de nom entre les deux Stack. 2 } } } Il ne vous reste plus qu’à implémenter les trois méthodes recquises par l’interface Queue. Ce n’est pas si difficile mais, si vous ne faites pas des dessins soigneux pour comprendre votre systèmes de références (pointeurs), il est très peu probable que vous y parveniez. 3 Exercices facultatifs – Comparez les performances de QueueA et QueueB. – Proposez une troisième implémentation des files d’attente en utilisant une extension de la classe Vector de l’API Java : import java.util.Vector ; public class QueueC extends Vector implements Queue { // pas de nouvel attribut public QueueC () { super() ; } /* compléter par les méthodes nécessaires de Queue, définies au moyen des méthodes héritées de Vector */ } – Utilisez une pile pour vérifier qu’une expression contenue dans une chaîne est correctement parenthésée. Vous pourrez utiliser l’algorithme dont le corps est : variables i : entier c : chaîne de caractères p : pile de caractères début lire(c) initialiser(p) i 1 boucle si i > longueur(c) alors sortie boucle finsi si c[i] = ’ ( ’ alors empiler(p, ’ ( ’ ) sinonsi c[i] = ’ ) ’ alors si pilevide(p) alors sortie boucle sinon dépiler(p) finsi finsi i 1 finboucle { bilan à votre charge } fin – Ajoutez au deux interfaces Stack et Queue une méthode : public abstract int size () ; // taille courante de la pile ou de la file d’attente et, bien sûr, implémentez-la partout où il le faut. 3