Dans vos applications Java, vous travaillerez généralement avec divers types d’objets. Et vous voudrez peut-être effectuer des opérations telles que le tri, la recherche et l’itération sur ces objets.

Avant l’introduction du framework Collections dans JDK 1.2, vous auriez utilisé des tableaux et des vecteurs pour stocker et gérer un groupe d’objets. Mais ils avaient leurs propres inconvénients.

Le framework de collections Java vise à surmonter ces problèmes en fournissant des implémentations de haute performance de structures de données courantes. Cela vous permet de vous concentrer sur l’écriture de la logique de l’application au lieu de vous concentrer sur des opérations de bas niveau.

Ensuite, l’introduction des Génériques dans JDK 1.5 a considérablement amélioré le framework de collections Java. Les Génériques vous permettent d’imposer la sécurité des types pour les objets stockés dans une collection, ce qui renforce la robustesse de vos applications. Vous pouvez en lire plus sur les Génériques Java ici.

Dans cet article, je vais vous guider sur la façon d’utiliser le framework de collections Java. Nous discuterons des différents types de collections, tels que les Listes, les Ensembles, les Files d’attente et les Cartes. Je fournirai également une brève explication de leurs caractéristiques clés telles que :

  • Mécanismes internes

  • Gestion des doublons

  • Support des valeurs nulles

  • Ordonnancement

  • Synchronisation

  • Performance

  • Key methods

  • Common implementations

Nous passerons également en revue quelques exemples de code pour une meilleure compréhension, et j’aborderai la classe utilitaire Collections et son utilisation.

Table des matières:

  1. Comprendre le Framework des collections Java

  2. Interfaces de collection Java

  3. Classe utilitaire des collections

  4. Conclusion

Comprendre le cadre des collections Java

Selon la documentation Java, “Une collection est un objet qui représente un groupe d’objets. Un cadre de collections est une architecture unifiée pour représenter et manipuler des collections.”

En termes simples, le cadre des collections Java vous aide à gérer un groupe d’objets et à effectuer des opérations sur eux de manière efficace et organisée. Il facilite le développement d’applications en offrant différentes méthodes pour manipuler des groupes d’objets. Vous pouvez ajouter, supprimer, rechercher et trier des objets efficacement en utilisant le cadre des collections Java.

Interfaces de collection

En Java, une interface spécifie un contrat qui doit être respecté par toute classe qui l’implémente. Cela signifie que la classe implémentante doit fournir des implémentations concrètes pour toutes les méthodes déclarées dans l’interface.

Dans le cadre du Java Collections Framework, diverses interfaces de collection telles que Set, List et Queue étendent l’interface Collection et doivent respecter le contrat défini par l’interface Collection.

Décodage de la hiérarchie du Java Collections Framework

Découvrez ce joli diagramme provenant de cet article qui illustre la hiérarchie des collections Java :

Nous commencerons par le haut et travaillerons vers le bas afin que vous puissiez comprendre ce que ce diagramme montre :

  1. À la racine du Java Collections Framework se trouve l’interface Iterable, qui vous permet d’itérer sur les éléments d’une collection.

  2. L’interface Collection étend l’interface Iterable. Cela signifie qu’elle hérite des propriétés et du comportement de l’interface Iterable et ajoute son propre comportement pour ajouter, supprimer et récupérer des éléments.

  3. Des interfaces spécifiques telles que List, Set et Queue étendent encore l’interface Collection. Chacune de ces interfaces a d’autres classes qui implémentent leurs méthodes. Par exemple, ArrayList est une implémentation populaire de l’interface List, HashSet implémente l’interface Set, et ainsi de suite.

  4. L’interface Map fait partie du framework de collections Java, mais elle n’étend pas l’interface Collection, contrairement aux autres mentionnées ci-dessus.

  5. Toutes les interfaces et classes de ce framework font partie du package java.util.

Remarque : Une source courante de confusion dans le Framework de Collections Java tourne autour de la différence entre Collection et Collections. Collection est une interface dans le framework, tandis que Collections est une classe utilitaire. La classe Collections fournit des méthodes statiques qui effectuent des opérations sur les éléments d’une collection.

Interfaces de Collection Java

À présent, vous êtes familier avec les différents types de collections qui constituent la base du framework de collections. Maintenant, nous allons examiner de plus près les interfaces List, Set, Queue et Map.

Dans cette section, nous discuterons de chacune de ces interfaces tout en explorant leurs mécanismes internes. Nous examinerons comment elles gèrent les éléments en double et si elles prennent en charge l’insertion de valeurs nulles. Nous comprendrons également l’ordonnancement des éléments lors de l’insertion et leur prise en charge pour la synchronisation, qui traite du concept de sécurité des threads. Ensuite, nous passerons en revue quelques méthodes clés de ces interfaces et conclurons en examinant les implémentations courantes et leurs performances pour diverses opérations.

Avant de commencer, parlons brièvement de la Synchronisation et des Performances.

  • La synchronisation contrôle l’accès aux objets partagés par plusieurs threads, garantissant leur intégrité et prévenant les conflits. Cela est crucial pour maintenir la sécurité des threads.

  • Lors du choix d’un type de collection, un facteur important est sa performance lors d’opérations courantes telles que l’insertion, la suppression et la récupération. La performance est généralement exprimée en utilisant la notation Big-O. Vous pouvez en apprendre davantage à ce sujet ici.

Listes

Un Liste est une collection ordonnée ou séquentielle d’éléments. Il suit un indexage à partir de zéro, permettant l’insertion, la suppression ou l’accès aux éléments en utilisant leur position d’index.

  1. Mécanisme interne: Un Liste est supporté en interne par un tableau ou une liste chaînée, selon le type d’implémentation. Par exemple, un ArrayListe utilise un tableau, tandis qu’un LinkedList utilise une liste chaînée en interne. Vous pouvez en savoir plus sur LinkedList ici. Un Liste redimensionne dynamiquement sa taille lors de l’ajout ou de la suppression d’éléments. La récupération basée sur l’index en fait un type de collection très efficace.

  2. Doublons : Les éléments en double sont autorisés dans une List, ce qui signifie qu’il peut y avoir plus d’un élément dans une List avec la même valeur. Toute valeur peut être récupérée en fonction de l’index auquel elle est stockée.

  3. Null : Les valeurs nulles sont également autorisées dans une List. Étant donné que les doublons sont permis, vous pouvez également avoir plusieurs éléments nuls.

  4. Ordre : Une List maintient l’ordre d’insertion, ce qui signifie que les éléments sont stockés dans le même ordre dans lequel ils sont ajoutés. Cela est utile lorsque vous souhaitez récupérer les éléments dans l’ordre exact dans lequel ils ont été insérés.

  5. Synchronisation: Une Liste n’est pas synchronisée par défaut, ce qui signifie qu’elle n’a pas de méthode intégrée pour gérer l’accès par plusieurs threads en même temps.

  6. Méthodes clés: Voici quelques méthodes clés d’une interface List: add(E element), get(int index), set(int index, E element), remove(int index) et size(). Voyons comment utiliser ces méthodes avec un programme d’exemple.

     import java.util.ArrayList;
     import java.util.List;
    
     public class ListExample {
         public static void main(String[] args) {
             // Créer une liste
             List<String> list = new ArrayList<>();
    
             // add(E element)
             list.add("Pomme");
             list.add("Banane");
             list.add("Cerise");
    
             // get(int index)
             String secondElement = list.get(1); // "Banane"
    
             // set(int index, E element)
             list.set(1, "Myrtille");
    
             // remove(int index)
             list.remove(0); // Supprime "Pomme"
    
             // size()
             int size = list.size(); // 2
    
             // Afficher la liste
             System.out.println(list); // Résultat: [Myrtille, Cerise]
    
             // Afficher la taille de la liste
             System.out.println(size); // Résultat: 2
         }
     }
    
  7. Implémentations courantes: ArrayList, LinkedList, Vector, Stack

  8. Performance: En général, les opérations d’insertion et de suppression sont rapides à la fois dans les ArrayList et les LinkedList. Mais l’accès aux éléments peut être lent car vous devez traverser les nœuds.

Operation ArrayList LinkedList
Insertion Rapide à la fin – O(1) amorti, lent au début ou au milieu – O(n) Rapide au début ou au milieu – O(1), lent à la fin – O(n)
Suppression Rapide à la fin – O(1) amorti, lent au début ou au milieu – O(n) Rapide – O(1) si la position est connue
Récupération Rapide – O(1) pour un accès aléatoire Lent – O(n) pour un accès aléatoire, car cela implique de traverser

Ensembles

Un Set est un type de collection qui ne permet pas les éléments en double et représente le concept d’un ensemble mathématique.

  1. Mécanisme interne: Un Set est soutenu en interne par un HashMap. Selon le type d’implémentation, il est supporté par un HashMap, un LinkedHashMap ou un TreeMap. J’ai écrit un article détaillé sur le fonctionnement interne du HashMap ici. Assurez-vous de le consulter.

  2. Doublons: Puisqu’un Set représente le concept d’un ensemble mathématique, les éléments en double ne sont pas autorisés. Cela garantit que tous les éléments sont uniques, maintenant l’intégrité de la collection.

  3. Null: Un maximum d’une valeur nulle est autorisé dans un Set car les doublons ne sont pas permis. Mais cela ne s’applique pas à l’implémentation de TreeSet, où les valeurs nulles ne sont pas du tout autorisées.

  4. Ordering: L’ordonnancement des éléments dans un Set dépend du type d’implémentation.

    • HashSet: L’ordre n’est pas garanti, et les éléments peuvent être placés n’importe où.

    • LinkedHashSet: Cette implémentation maintient l’ordre d’insertion, vous pouvez donc récupérer les éléments dans le même ordre où ils ont été insérés.

    • TreeSet: Les éléments sont insérés en fonction de leur ordre naturel. Alternativement, vous pouvez contrôler l’ordre d’insertion en spécifiant un comparateur personnalisé.

  5. Synchronisation: Un Set n’est pas synchronisé, ce qui signifie que vous pourriez rencontrer des problèmes de concurrence, comme des conditions de course, qui peuvent affecter l’intégrité des données si deux threads ou plus tentent d’accéder simultanément à un objet Set

  6. Méthodes clés: Voici quelques méthodes clés d’une interface Set: add(E élément), remove(Object o), contains(Object o), et size(). Voyons comment utiliser ces méthodes avec un programme d’exemple.

     import java.util.HashSet;
     import java.util.Set;
    
     public class SetExample {
         public static void main(String[] args) {
             // Créer un ensemble
             Set<String> ensemble = new HashSet<>();
    
             // Ajouter des éléments à l'ensemble
             ensemble.add("Pomme");
             ensemble.add("Banane");
             ensemble.add("Cerise");
    
             // Retirer un élément de l'ensemble
             ensemble.remove("Banane");
    
             // Vérifier si l'ensemble contient un élément
             boolean contientPomme = ensemble.contains("Pomme");
             System.out.println("Contient Pomme: " + contientPomme);
    
             // Obtenir la taille de l'ensemble
             int taille = ensemble.size();
             System.out.println("Taille de l'ensemble: " + taille);
         }
     }
    
  7. Implémentations courantes: HashSet, LinkedHashSet, TreeSet

  8. Performance: Les implémentations de Set offrent des performances rapides pour les opérations de base, à l’exception d’un TreeSet, où les performances peuvent être relativement plus lentes car la structure de données interne implique de trier les éléments pendant ces opérations.

Opération HashSet LinkedHashSet TreeSet
Insertion Constante – O(1) Constante – O(1) Plus lent – O(log n)
Suppression Constante – O(1) Constante – O(1) Plus lent – O(log n)
Récupération Constante – O(1) Constante – O(1) Plus lent – O(log n)

File d’attente

Une Queue est une collection linéaire d’éléments utilisée pour contenir plusieurs éléments avant le traitement, suivant généralement l’ordre FIFO (premier entré, premier sorti). Cela signifie que les éléments sont ajoutés à une extrémité et supprimés de l’autre, de sorte que le premier élément ajouté à la file d’attente est le premier à être retiré.

  1. Mécanisme interne: Le fonctionnement interne d’une Queue peut différer en fonction de sa mise en œuvre spécifique.

    • LinkedList – utilise une liste doublement chaînée pour stocker des éléments, ce qui signifie que vous pouvez parcourir à la fois vers l’avant et vers l’arrière, permettant des opérations flexibles.

    • PriorityQueue – est soutenu en interne par un tas binaire, ce qui est très efficace pour les opérations de récupération.

    • ArrayDeque – est implémenté en utilisant un tableau qui se développe ou se rétrécit lorsque des éléments sont ajoutés ou supprimés. Ici, les éléments peuvent être ajoutés ou supprimés des deux extrémités de la file d’attente.

  2. Doublons: Dans une Queue, des éléments en double sont autorisés, permettant l’insertion de plusieurs instances de la même valeur

  3. Null: Vous ne pouvez pas insérer de valeur nulle dans une Queue car, par conception, certaines méthodes d’une Queue retournent null pour indiquer qu’elle est vide. Pour éviter toute confusion, les valeurs nulles ne sont pas autorisées.

  4. Tri: Les éléments sont insérés en fonction de leur ordre naturel. Sinon, vous pouvez contrôler l’ordre d’insertion en spécifiant un comparateur personnalisé.

  5. Synchronisation: Une Queue n’est pas synchronisée par défaut. Cependant, vous pouvez utiliser une implémentation de ConcurrentLinkedQueue ou de BlockingQueue pour assurer la sécurité des threads.

  6. Méthodes clés: Voici quelques méthodes clés d’une interface Queue : add(E element), offer(E element), poll(), et peek(). Voyons comment utiliser ces méthodes avec un programme d’exemple.

     import java.util.LinkedList;
     import java.util.Queue;
    
     public class QueueExample {
         public static void main(String[] args) {
             // Créer une file d'attente en utilisant LinkedList
             Queue<String> queue = new LinkedList<>();
    
             // Utiliser la méthode add pour insérer des éléments, lève une exception si l'insertion échoue
             queue.add("Élément1");
             queue.add("Élément2");
             queue.add("Élément3");
    
             // Utiliser la méthode offer pour insérer des éléments, renvoie false si l'insertion échoue
             queue.offer("Élément4");
    
             // Afficher la file d'attente
             System.out.println("File d'attente: " + queue);
    
             // Jeter un œil sur le premier élément (ne le supprime pas)
             String premierElement = queue.peek();
             System.out.println("Jeter un œil : " + premierElement); // affiche "Élément1"
    
             // Récupérer le premier élément (le retire)
             String elementRécupéré = queue.poll();
             System.out.println("Récupérer : " + elementRécupéré); // affiche "Élément1"
    
             // Afficher la file d'attente après récupération
             System.out.println("File d'attente après récupération : " + queue);
         }
     }
    
  7. Implémentations courantes: LinkedList, PriorityQueue, ArrayDeque

  8. Performance: Des implémentations comme LinkedList et ArrayDeque sont généralement rapides pour ajouter et supprimer des éléments. La PriorityQueue est un peu plus lente car elle insère les éléments en fonction de l’ordre de priorité défini.

Opération LinkedList PriorityQueue ArrayDeque
Insertion Rapide au début ou au milieu – O(1), lent à la fin – O(n) Plus lent – O(log n) Rapide – O(1), Lent – O(n), si cela implique le redimensionnement du tableau interne
Suppression Rapide – O(1) si la position est connue Plus lent – O(log n) Rapide – O(1), Lent – O(n), si cela implique le redimensionnement du tableau interne
Récupération Lent – O(n) pour un accès aléatoire, car cela implique une traversée Rapide – O(1) Rapide – O(1)

Cartes

Une Map représente une collection de paires clé-valeur, chaque clé étant associée à une seule valeur. Bien que Map fasse partie du framework Java Collection, elle n’étend pas l’interface java.util.Collection.

  1. Mécanisme interne: Une Map fonctionne en interne en utilisant une HashTable basée sur le concept de hachage. J’ai écrit un article détaillé sur ce sujet, alors lisez-le pour une compréhension plus approfondie.

  2. Doublons: Une Map stocke les données sous forme de paires clé-valeur. Ici, chaque clé est unique, donc les clés en double ne sont pas autorisées. Mais les valeurs en double sont permises.

  3. Nullité: Comme les clés en double ne sont pas autorisées, une Map ne peut avoir qu’une seule clé nulle. Comme les valeurs en double sont permises, elle peut avoir plusieurs valeurs nulles. Dans l’implémentation de TreeMap, les clés ne peuvent pas être nulles car elle trie les éléments en fonction des clés. Cependant, les valeurs nulles sont autorisées.

  4. Tri: L’ordre d’insertion d’une Map varie en fonction de l’implémentation:

    • HashMap – l’ordre d’insertion n’est pas garanti car il est déterminé en fonction du concept de hachage.

    • LinkedHashMap – l’ordre d’insertion est préservé et vous pouvez récupérer les éléments dans le même ordre où ils ont été ajoutés à la collection.

    • TreeMap – Les éléments sont insérés en fonction de leur ordre naturel. Alternativement, vous pouvez contrôler l’ordre d’insertion en spécifiant un comparateur personnalisé.

  5. Synchronisation : Une Map n’est pas synchronisée par défaut. Mais vous pouvez utiliser les implémentations Collections.synchronizedMap() ou ConcurrentHashMap pour assurer la sécurité des threads.

  6. Méthodes clés: Voici quelques méthodes clés d’une interface Map: put(K key, V value), get(Object key), remove(Object key), containsKey(Object key) et keySet(). Voyons comment utiliser ces méthodes avec un programme d’exemple.

     import java.util.HashMap;
     import java.util.Map;
     import java.util.Set;
    
     public class MapMethodsExample {
         public static void main(String[] args) {
             // Créer une nouvelle HashMap
             Map<String, Integer> map = new HashMap<>();
    
             // put(K key, V value) - Insère des paires clé-valeur dans la map
             map.put("Pomme", 1);
             map.put(Banane", 2);
             map.put(Orange", 3);
    
             // get(Object key) - Renvoie la valeur associée à la clé
             Integer value = map.get("Banane");
             System.out.println("Valeur pour 'Banane': " + value);
    
             // remove(Object key) - Supprime la paire clé-valeur pour la clé spécifiée
             map.remove("Orange");
    
             // containsKey(Object key) - Vérifie si la map contient la clé spécifiée
             boolean hasApple = map.containsKey("Pomme");
             System.out.println("Contient 'Pomme': " + hasApple);
    
             // keySet() - Renvoie une vue ensemble des clés contenues dans la map
             Set<String> keys = map.keySet();
             System.out.println("Clés dans la map: " + keys);
         }
     }
    
  7. Implémentations courantes: HashMap, LinkedHashMap, TreeMap, Hashtable, ConcurrentHashMap

  8. Performance: L’implémentation de HashMap est largement utilisée principalement en raison de ses caractéristiques de performance efficaces telles que décrites dans le tableau ci-dessous.

Opération HashMap LinkedHashMap TreeMap
Insertion Rapide – O(1) Rapide – O(1) Plus lent – O(log n)
Suppression Rapide – O(1) Rapide – O(1) Plus lent – O(log n)
Récupération Rapide – O(1) Rapide – O(1) Plus lent – O(log n)

Classe utilitaire Collections

Comme souligné au début de cet article, la classe utilitaire Collections possède plusieurs méthodes statiques utiles qui vous permettent d’effectuer des opérations couramment utilisées sur les éléments d’une collection. Ces méthodes vous aident à réduire le code standard dans votre application et vous permettent de vous concentrer sur la logique métier.

Voici quelques caractéristiques clés et méthodes, ainsi que ce qu’elles font, énumérées brièvement :

  1. Triage : Collections.sort(List<T>) – cette méthode est utilisée pour trier les éléments d’une liste par ordre croissant.

  2. Recherche : Collections.binarySearch(List<T>, key) – cette méthode est utilisée pour rechercher un élément spécifique dans une liste triée et retourner son index.

  3. Ordre inverse : Collections.reverse(List<T>) – cette méthode est utilisée pour inverser l’ordre des éléments dans une liste.

  4. Opérations Min/Max : Collections.min(Collection<T>) et Collections.max(Collection<T>) – ces méthodes sont utilisées pour trouver les éléments minimum et maximum dans une collection, respectivement.

  5. Synchronisation : Collections.synchronizedList(List<T>) – cette méthode est utilisée pour rendre une liste thread-safe en la synchronisant.

  6. Collections non modifiables: Collections.unmodifiableList(List<T>) – cette méthode est utilisée pour créer une vue en lecture seule d’une liste, empêchant les modifications.

Voici un programme Java d’exemple qui démontre diverses fonctionnalités de la classe utilitaire Collections:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CollectionsExample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(5);
        numbers.add(3);
        numbers.add(8);
        numbers.add(1);

        // Tri
        Collections.sort(numbers);
        System.out.println("Sorted List: " + numbers);

        // Recherche
        int index = Collections.binarySearch(numbers, 3);
        System.out.println("Index of 3: " + index);

        // Ordre inverse
        Collections.reverse(numbers);
        System.out.println("Reversed List: " + numbers);

        // Opérations Min/Max
        int min = Collections.min(numbers);
        int max = Collections.max(numbers);
        System.out.println("Min: " + min + ", Max: " + max);

        // Synchronisation
        List<Integer> synchronizedList = Collections.synchronizedList(numbers);
        System.out.println("Synchronized List: " + synchronizedList);

        // Collections non modifiables
        List<Integer> unmodifiableList = Collections.unmodifiableList(numbers);
        System.out.println("Unmodifiable List: " + unmodifiableList);
    }
}

Ce programme démontre le tri, la recherche, l’inversion, la recherche des valeurs minimales et maximales, la synchronisation et la création d’une liste non modifiable en utilisant la classe utilitaire Collections.

Conclusion

Dans cet article, vous avez appris sur le Framework des Collections Java et comment il aide à gérer des groupes d’objets dans les applications Java. Nous avons exploré divers types de collections comme les Listes, Ensembles, Files et Maps et avons acquis un aperçu de certaines des caractéristiques clés et comment chacun de ces types les supporte.

Vous avez appris sur les performances, la synchronisation et les méthodes clés, acquérant des informations précieuses pour choisir les bonnes structures de données selon vos besoins.

En comprenant ces concepts, vous pouvez pleinement utiliser le Framework des Collections Java, ce qui vous permet d’écrire un code plus efficace et de construire des applications robustes.

Si vous avez trouvé cet article intéressant, n’hésitez pas à consulter mes autres articles sur freeCodeCamp et à me contacter sur LinkedIn.