Salut,
au contraire, une interface ne définit pas le corps des méthodes, mais uniquement leur signature.
Le but d'une interface est définir les méthodes nécessaires à la manipulation d'une classe, sans imposer comment c'est fait dans ces méthodes.
Prenons par exemple une interface du jdk : java.awt.Shape. Le jdk fournit un ensemble d'outils qui permet de manipuler des formes (shapes en anglais), comme par exemple des méthodes pour dessiner des formes : dans la classe java.awt.Graphics2D, les méthodes draw, pour dessiner le périmètre d'une forme, et fill, pour remplir une forme.
Il suffit pour faire une forme, de faire une classe qui implémente l'interface java.awt.Shape pour que ces méthodes draw et fill puissent dessiner la forme implémentée. Le jdk fournit un certain nombre d'implémentations de cette interface, pour dessiner des rectangles, des cercles, des polygones...
Cette interface, shape, a pas mal de méthodes à implémenter, donc pour l'exemple, on va pas l'implémenter (regardes les sources des classes Rectangle2D.Double, ou Ellipse2D.Double si ça t'intéresse).
On va plutôt regarder l'interface java.util.Iterator qui a 3 méthodes assez simples à comprendre. Cette interface définit une classe qui permet d'itérer sur une liste de valeurs, en récupérant un élément, puis le suivant, puis son suivant, etc...tant qu'il y a des éléments, et permet en plus de supprimer éventuellement l'élément qu'on vient de récupérer. Elle permet donc d'itérer sur une liste de valeurs.
Tout d'abord, l'interface Iterator est une interface paramétrée : elle permet de récupérer des valeurs d'un type déterminé quelconque, que l'on paramètre.
public interface Iterator<E>
Ici le E entre < et > dit que l'interface a pour paramètre E (une classe dont on ne sait encore rien) : il s'agit du type des valeurs qu'on va obtenir à chaque itération.
La première méthode de cette interface est :
Cette méthode permet de savoir s'il existe encore des éléments à récupérer, ou si on est arrivé au bout de la liste de valeurs.
La méthode suivante permet de récupérer la prochaine valeur disponible, s'il en existe (donc si hasNext() retourne true)
Le type de ce que cette méthode retourne est E, la classe qui paramètre notre interface.
La doc de l'interface nous dit que si next() est appelée alors que hasNext() retourne false, on doit soulever l'exception NoSuchElementException.
La troisième méthode est censée permettre de supprimer le dernier élément obtenu par un appel de next(). La doc dit qu'on est pas obligé de l'implémenter et qu'on peut soulever l'exception UnsupportedOperationException dans ce cas.
L'implémentation la plus simple de cette interface, pour l'exemple, est de faire un iterateur toujours vide : ça ne sert à rien, mais c'est pour l'exemple.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| // on définit un itérateur vide (sans valeurs) dont on dit que
// le type paramétré n'est pas encore spécifié
public class EmptyIterator<E> implements Iterator<E> {
// l'itérateur est vide dont hasNext retourne toujours false
public boolean hasNext() {
return false;
}
// l'itérateur est vide donc next soulève toujours l'exception NoSuchElementException
public E next() {
throw new NoSuchElementException();
}
// on a décidé de ne pas implémenter cette méthode
public void remove() {
throw new UnsupportedOperationException();
}
} |
et ça s'utilisera de cette manière :
1 2 3 4
| Iterator<Integer> iterator = new EmptyIterator<Integer>;
while( iterator.hasNext()) {
System.out.println(iterator.next());
} |
ici la boucle manipule l'itération via l'interface : elle fonctionne quelque soit l'implémentation.
Par exemple, si on fait :
1 2 3 4 5
| List<Integer> list = new ArrayList<Integer>(); // une liste vide
Iterator<Integer> iterator = list.iterator(); // un iterator sur la liste, vide
while( iterator.hasNext()) {
System.out.println(iterator.next());
} |
On aura exactement le même résultat (c'est-à-dire rien qui s'affiche)
Maintenant faisons pour l'exemple un iterateur qui retourne un nombre de valeurs aléatoires de type Integer. Le nombre de valeurs sera passée en argument du constructeur, ainsi que la valeur min et la valeur max.
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 32 33 34 35 36 37 38 39 40
| public class RandomIntIterator implements Iterator<Integer> {
private int nb;
private int min;
private int max;
private Random random;
private int i;
public RandomIntIterator(int nb, int min, int max) {
this.nb=nb; // nombre de valeurs que l'itérateur va générer
this.min=min; // valeur min générée à chaque itération
this.max=max; // valeur max générée à chaque itération
this.random=new Random();
this.i=0; // nombre de valeurs déjà générée par cette instance de l'itérateur
}
@Override
public boolean hasNext() {
return i<nb; // tant que i < nb, on a encore des valeurs à générer
}
@Override
public Integer next() {
if ( i<nb ) { // s'il reste encore des valeurs à générer
int v = min + random.nextInt(max-min); // on génère une valeur aléatoire
i++; // on incrémente notre compteur de valeur générée
return v; // on retourne la valeur générée
}
else {
throw new NoSuchElementException("Plus de valeur aléatoire dans cet itérateur");
}
}
@Override
public void remove() {
// on ne support pas l'effacement (ça n'aurait aucun sens pour cet itérateur)
throw new UnsupportedOperationException();
}
} |
et voici une implémentation d'itérator qui permet d'iterer succesivement sur plusieurs itérateurs :
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| public class IteratorConcat<E> implements Iterator<E> {
private Iterator<E>[] iterators;
private int i;
private Iterator<E> lastIterator;
public IteratorConcat(Iterator<E>...iterators) {
this.iterators=iterators;
this.i=0;
}
@Override
public final boolean hasNext() {
if ( i<iterators.length ) {
if ( iterators[i].hasNext() ) {
return true;
}
else {
if ( i+1<iterators.length ) {
return iterators[i+1].hasNext();
}
return false;
}
}
return false;
}
@Override
public E next() {
if (i<iterators.length&&iterators[i].hasNext()) {
E n = iterators[i].next();
lastIterator=iterators[i];
if ( !iterators[i].hasNext() ) { // si l'itérateur courant n'a plus de valeur, on passe au suivant
i++;
}
return n;
}
else {
throw new NoSuchElementException();
}
}
@Override
public void remove() {
if (lastIterator==null) throw new IllegalStateException();
try {
lastIterator.remove();
}
finally {
lastIterator=null;
}
}
} |
Pour finir, créer ses propres interfaces : imaginons que tu fasses une application qui permet d'imprimer des cartes de visites, avec un nom, un prénom, un numéro de téléphone.
Tu vas faire toutes les classes qui permettent d'imprimer une carte de visite, suivant différent modèle de cartes de visite, qui imprime 3 informations, un nom, un prénom et un numéro de téléphone. Tu obtiendras ces informations en les lisant via une interface, mettons Identite définit comme suit :
1 2 3 4 5
| public interface Identite {
String getNom();
String getPrenom();
String getTelephone();
} |
Tu auras dans une de tes classe :
1 2 3
| public void imprimer(List<Identite> identites) {
...
} |
Et quelqu'un qui voudra imprimer les cartes de visites pour des personnes stockées dans sa base de données, appellera ta méthode imprimer en passant un liste d'instance de classe implémentant Identite (classe qu'il aura faite), telles que ses méthodes retournent les valeurs qu'il aura lues dans la base de données (dont ton code ne connait rien forcément) (peut être que seul le nom et le prénom auront été lus, et que le téléphone sera une String en dur, parce que tout le monde à le même numéro, par exemple)
Partager