Vous utilisez un Set lorsque vous ne voulez pas autoriser les entrées en double. Par exemple, vous pourriez vouloir garder une trace des animaux uniques que vous souhaitez voir au zoo. Vous ne vous souciez pas de l’ordre dans lequel vous voyez ces animaux, mais il n’y a pas le temps de les voir plus d’une fois. Vous voulez juste vous assurer de voir ceux qui sont importants pour vous et les retirer de l’ensemble des animaux à voir après les avoir vus.
Ce que tous les types d’implémentation de Set ont en commun, c’est qu’ils n’autorisent pas les doublons. Nous allons examiner chaque implémentation et comment écrire du code en utilisant Set.
Comparaison des Implémentations de Set
Un HashSet stocke ses éléments dans une table de hachage, ce qui signifie que les clés sont un hash et les valeurs sont un Object. Cela signifie que le HashSet utilise la méthode hashCode() des objets pour les récupérer plus efficacement. Rappelez-vous qu’un hashCode() valide ne signifie pas que chaque objet obtiendra une valeur unique, mais la méthode est souvent écrite de sorte que les valeurs de hachage sont réparties sur une large plage pour réduire les collisions.
Le principal avantage est que l’ajout d’éléments et la vérification de la présence d’un élément dans l’ensemble ont tous deux un temps constant. La contrepartie est que vous perdez l’ordre dans lequel vous avez inséré les éléments. La plupart du temps, vous ne vous en souciez pas dans un Set de toute façon, ce qui fait du HashSet le set le plus courant.
Un TreeSet stocke ses éléments dans une structure d’arbre triée. Le principal avantage est que l’ensemble est toujours dans un ordre trié. L’inconvénient est que l’ajout et la vérification de l’existence d’un élément prend plus de temps qu’avec un HashSet, surtout à mesure que l’arbre grandit.
Travailler avec les Méthodes de Set
Comme une List, vous pouvez créer un Set immuable en une ligne ou faire une copie d’un Set existant.
Set<Character> lettres = Set.of('z', 'o', 'o');
Set<Character> copie = Set.copyOf(lettres);
Vous devez savoir comment les sets se comportent par rapport aux méthodes traditionnelles de Collection. Vous devez également connaître les différences entre les types de sets. Commençons par HashSet:
Set<Integer> ensemble = new HashSet<>();
boolean b1 = ensemble.add(66); // true
boolean b2 = ensemble.add(10); // true
boolean b3 = ensemble.add(66); // false
boolean b4 = ensemble.add(8); // true
ensemble.forEach(System.out::println);
Ce code imprime trois lignes :
66
8
10
Les méthodes add() devraient être simples à comprendre. Elles renvoient true sauf si l’Integer est déjà dans le set. La ligne 6 renvoie false, car nous avons déjà 66 dans l’ensemble, et un set doit préserver l’unicité. La ligne 8 imprime les éléments du set dans un ordre arbitraire. Dans ce cas, ce n’est ni l’ordre de tri ni l’ordre dans lequel nous avons ajouté les éléments.
N’oubliez pas que la méthode equals() est utilisée pour déterminer l’égalité. La méthode hashCode() est utilisée pour savoir dans quel compartiment chercher afin que Java n’ait pas à parcourir l’ensemble du set pour déterminer si un objet s’y trouve. Le meilleur des cas est que les codes de hachage sont uniques et Java doit appeler equals() sur un seul objet. Le pire des cas est que toutes les implémentations renvoient le même hashCode() et Java doit quand même appeler equals() sur chaque élément du set.
Regardons maintenant le même exemple avec TreeSet :
Set<Integer> ensemble = new TreeSet<>();
boolean b1 = ensemble.add(66); // true
boolean b2 = ensemble.add(10); // true
boolean b3 = ensemble.add(66); // false
boolean b4 = ensemble.add(8); // true
ensemble.forEach(System.out::println);
Cette fois, le code imprime :
8
10
66
Les éléments sont imprimés dans leur ordre naturel de tri. Les nombres implémentent l’interface Comparable en Java, qui est utilisée pour le tri. Plus tard, vous apprendrez comment créer vos propres objets Comparable.