Salut,
Attention gros pavé !!!!
Une dernière remarque : le fait d'utiliser des interfaces permet de donner une plus grande abstractions à notre code. Mis à part quelques cas particulier on devrait toujours utiliser les interfaces dans notre code.
Par exemple prenons cette méthode dont l'objectif est d'afficher une liste d'objet. Le code est tout bête :
Le premier défaut de cette méthode vient du fait qu'elle attend une ArrayList. Or le développeur qui va vouloir utiliser cette méthode pourrait avoir une autre type de List (pour quelque raison que ce soit), qu'il devra alors recopier dans une ArrayList avant de l'afficher :?Code:
1
2
3
4
5
6
7 public void print(ArrayList list) { for (int i=0; i<list.size(); i++) { System.out.println(list.get(i)); } }
Et tout cela uniquement car on a déclaré utiliser une ArrayList. On dans le cas présent on se contrefout du type précis de la liste. En modifiant le type du paramètre ArrayList en List on obtient un code un peu plus générique :
:arrow: On peut donc désormais utiliser notre méthode avec n'importe quel type de List, même des implémentations qui nous serait inconnu !Code:
1
2
3
4
5
6
7 public void print(List list) { for (int i=0; i<list.size(); i++) { System.out.println(list.get(i)); } }
On rencontre alors le second problème : l'accès direct aux éléments via get() qui peut se révéler vraiment catastrophique selon l'implémentation (par exemple dans le cas d'une LinkedList). Lorsqu'on parcourt les éléments d'une List, il est fortement recommandé de passer par un Iterator :
A partir de là, on peut encore élargir les possibilités d'utilisation : l'accès par index est inutile dans notre méthode, et on peut donc remplacé List par Collection, ce qui nous permettra de l'utiliser avec encore plus d'implémentations différentes (dont les Set), le tout via une modification mineure :Code:
1
2
3
4
5
6
7
8 public void print(List list) { Iterator i = list.iterator(); while (i.hasNext()) { System.out.println(i.next()); } }
Code:
1
2
3
4
5
6
7
8 public void print(Collection col) { Iterator i = col.iterator(); while (i.hasNext()) { System.out.println(i.next()); } }
Bref en modifiant légèrement le type du paramètre, et en parcourant correctement la liste, on passe d'une méthode limité aux ArrayList à une méthode bien plus générique pouvant accepter un très grand nombre d'implémentation.
Et Depuis Java 5.0 on peut faire encore mieux.
A commencer par les erreurs, puisque avec Java 5.0 les Collections sont Generics on serait tenter de faire ceci :
:arrow: Problème : Collection<Object> impose que la collection soit paramétré en <Object> précisément, ce qui limite une nouvelle fois notre méthode. Par exemple il est impossible dans l'état actuel de lui passer une Collection<String> ou une Collection<Date> car le typage des paramètres Generics est très fort...Code:
1
2
3
4
5
6
7
8 public void print(Collection<Object> col) { Iterator<Object> i = col.iterator(); while (i.hasNext()) { System.out.println(i.next()); } }
Il faut utiliser le wildcard <? extends Object> qui indique qu'on accepte n'importe quelle liste paramétré avec un objet étendant de Object. Cela nous limitera toutefois l'accès à certaines méthodes permettant de modifier la collection (car on ne connait pas le paramétrage précis de la collection), mais cela nous est complètement inutile :
A noter que dans le cas particulier de Object, <? extends Object> peut s'écrire <?> et que l'on peut également utiliser le for-étendu de Java 5.0 :Code:
1
2
3
4
5
6
7
8 public void print(Collection<? extends Object> col) { Iterator<? extends Object> i = col.iterator(); while (i.hasNext()) { System.out.println(i.next()); } }
Enfin, Java 5.0 inclut également une interface Iterable utilisé par tous les éléments contenant un Iterator, ce qui semble parfaitement adapté à notre cas :Code:
1
2
3
4
5
6
7 public void print(Collection<?> col) { for (Object obj : col) { System.out.println(obj); } }
Code:
1
2
3
4
5
6
7 public void print(Iterable<?> elements) { for (Object obj : elements) { System.out.println(obj); } }
Et nous voilà avec une méthode encore plus générique !
En fait il faudrait toujours essayer d'utiliser l'interface la plus basique selon nos besoins, afin de rester le plus générique possible.
Il n'y a que quelques cas où l'on a réellement besoin du type concret :
- Lors de l'instanciation de l'objet (le new XXX()).
- Lorsqu'on a besoin d'une méthode spécifique de cette objet (par exemple le trimToSize() d'ArrayList).
- Lorsque une certain implémentation est requise pour notre besoin (mais c'est rarement le cas).
a++