L’API de collection est séparée en 2 types de fonctionnalités : les collections à proprement parler avec les interfaces Collection, List, Set et leurs sous interfaces, et les maps. Les bonnes pratiques imposent de déclarer les variables avec une interface et de n’utiliser les classes que pour l’instanciation.
Classes et interfaces
Les couples classe / interface classiques sont les suivants :
Collection<String> col = new ArrayList<>();
List<String> list = new ArrayList<>();
Set<String> set = new HashSet<>();
Map<String, String> map = new HashMap<>();
L’utilisation de classes anciennes, comme Vector ou Hashtable, est généralement à proscrire.
Avec le JDK 11, la façon la plus simple d’instancier une collection est d’utiliser les nouvelles méthodes de fabrique. Avec ces méthodes, on ne se préoccupe plus du type concret de la collection obtenu, il faut juste savoir qu’elle n’est pas modifiable.
List<String> list = List.of("AZERTY", "QSDFGH", "WXCVBN");
Et ça fonctionne aussi pour créer une Map
.
Map<String, String> map = Map.of(
"A", "AZERTY",
"Q", "QSDFGH",
"W", "WXCVBN"
);
Boucles for each
Depuis le JDK 5, une nouvelle forme de boucle for, surnommée « for each » a été introduite. Elle est beaucoup plus pratique que l’ancienne forme, mais présente des contraintes lorsqu’on veut manipuler la collection qu’on parcourt car l’itérateur n’est pas accessible.
for (Type var : col) {
col.remove(var); // Ceci est interdit !!!
}
Le problème rencontré est une ConcurrentAccessException
lorsqu’on supprime un élément de la collection en cours d’itération.
L’ancienne boucle, du fait qu’elle sépare distinctement la collection de son itérateur, permet d’ajouter ou de retirer depuis le corps de la boucle de éléments de la collection.
Ceci fonctionne à condition de la faire via l’itérateur et non directement sur la collection.
for (Iterator<Type> it = col.iterator();it.hasNext();) {
Type var = it.next();
...
col.remove(var); // Ceci est toujours interdit
it.remove(); // Ceci est autorisé
...
}
Tri
Le tri d’une liste se fait en passant la liste à trier à la méthode java.util.Collections.sort(list)
.
Cette méthode trie les objets de la liste selon le résultat de leurs méthodes compareTo()
.
Cela signifie donc que ces objets doivent implémenter l’interface Comparable.
Collections.sort(list);
Pour trier des listes d’objets qui n’implémente pas cette interface, on peut choisir d’utiliser un Comparator
et en passer une instance à la méthode java.util.Collections.sort(list, comparator)
.
Stream et lambda
Avec le JDK 8, l’introduction de l’API Stream
a changé par mal de choses dans la manipiulation des collections.
newCol = col.stream()
.filter(...)
.sort()
.toList();
Pour les comparateurs, la notation lambda simplifie les choses, puisque Comparator
est une interface fonctionnelle.
Comparator comp = (Person p1, Person p2) -> p2.getAge() - p1.getAge();
Et le JDK 8 a introduit des méthodes qui facilite l’écriture de comparateurs.
Comparator comp = Comparator.comparing(Person::getAge).reversed();
List
Implémentations de Comparée à LinkedList
, ArrayList
presque toujours meilleure, quelle que soit la taille de la liste.
Le seul cas où LinkedList
est éventuellement meilleure, c’est quand on modifie les éléments intermédiaires de la liste :
ajout ou suppression d’objets en début ou au milieu de la liste.
java.util.concurrent.CopyOnWriteArrayList
est une variante thread-safe de java.util.ArrayList
.
Chaque méthode de modification fait une copie du tableau interne.
Set
Implémentations de L’implémentation par défaut est HashSet
.
Avec LinkedHashSet
, les objets sont itérés dans l’ordre de leur ajout.
TreeSet
implémente SortedSet
qui, comme sont nom l’indique, stocke les objets triés dans leur ordre naturel ou selon un comparateur.
EnumSet
est utilisé pour les types énumérés.
Il est meilleur pour ça parce qu’il ne passe pas par equals()
mais ==
.
Il y a deux implémentations thread-safe :
-
CopyOnWriteArraySet
fait une copie de son tableau interne à chaque modification, -
ConcurrentSkipListSet
implémenteSortedSet
.
Il y a une troisième option, à partir d’un ConcurrentHashMap.
Set<String> set = ConcurrentHashMap.newKeySet();
Map
Implémentations de L’implémentation par défaut est HashMap
.
Avec LinkedHashMap
, les objets sont itérés dans l’ordre de leur ajout.
TreeMap
implémente SortedMap
qui, comme sont nom l’indique, stocke les clés triées dans leur ordre naturel ou selon un comparateur.
EnumMap
est utilisé pour les clés énumérés.
IdentityHashMap
compare aussi les clés par ==
, ce qui est performant mais contraire au contrat de Map
.
Les implémentations thread-safe :
-
ConcurrentHashMap
est l’implémentation par défaut, -
ConcurrentSkipListMap
implémenteSortedMap
.