In uw Java-toepassingen werkt u doorgaans met verschillende soorten objecten. En u wilt misschien bewerkingen uitvoeren zoals sorteren, zoeken en itereren op deze objecten.

Voor de introductie van het Collections-framework in JDK 1.2 zou u Arrays en Vectors hebben gebruikt om een groep objecten op te slaan en te beheren. Maar ze hadden hun eigen nadelen.

Het Java Collections Framework heeft als doel deze problemen te overwinnen door hoogwaardige implementaties van veelvoorkomende datastructuren te bieden. Deze stellen u in staat om u te concentreren op het schrijven van de applicatielogica in plaats van op laag-niveau bewerkingen.

De introductie van Generics in JDK 1.5 verbeterde het Java Collections Framework aanzienlijk. Generics stellen u in staat om typeveiligheid af te dwingen voor objecten die in een collectie zijn opgeslagen, wat de robuustheid van uw applicaties vergroot. U kunt hier meer lezen over Java Generics hier.

In dit artikel zal ik u begeleiden in het gebruik van het Java Collections Framework. We zullen de verschillende soorten collecties bespreken, zoals Lijsten, Sets, Wachtrijen en Kaarten. Ik zal ook een korte uitleg geven van hun belangrijkste kenmerken, zoals:

  • Interne mechanismen

  • Omgaan met duplicaten

  • Ondersteuning voor null-waarden

  • Volgorde

  • Synchronisatie

  • Prestatie

  • Belangrijke methoden

  • Veelvoorkomende implementaties

We zullen ook enkele codevoorbeelden doornemen voor een beter begrip, en ik zal het hebben over de Collections utility class en het gebruik ervan.

Inhoudsopgave:

  1. Begrijpen van het Java Collections Framework

  2. Java Collectie-Interfaces

  3. Collections Utility Klasse

  4. Conclusie

Begrijpen van het Java Collections Framework

Volgens de Java documentatie is “Een collectie is een object dat een groep objecten vertegenwoordigt. Een collectiestructuur is een eenduidige architectuur voor het vertegenwoordigen en manipuleren van collecties.”

In eenvoudige termen helpt het Java Collections Framework je om een groep objecten te beheren en efficiënt en georganiseerd bewerkingen op hen uit te voeren. Het maakt het gemakkelijker om applicaties te ontwikkelen door verschillende methoden aan te bieden voor het omgaan met groepen objecten. Je kunt objecten effectief toevoegen, verwijderen, doorzoeken en sorteren met behulp van het Java Collections Framework.

Collectie-interfaces

In Java specificeert een interface een contract dat moet worden nageleefd door elke klasse die het implementeert. Dit betekent dat de implementeerde klasse concrete implementaties moet bieden voor alle methoden die in de interface zijn verklaard.

In het Java Collections Framework breiden verschillende collectie-interfaces zoals Set, List en Queue de Collection interface uit, en ze moeten voldoen aan het contract dat door de Collection interface is gedefinieerd.

Decoderen van de Java Collections Framework Hiërarchie

Bekijk dit nette diagram uit dit artikel dat de Java Collectie Hiërarchie illustreert:

We beginnen van boven en werken naar beneden zodat je kunt begrijpen wat dit diagram laat zien:

  1. Aan de basis van het Java Collections Framework staat de Iterable interface, die je in staat stelt om over de elementen van een collectie te itereren.

  2. De Collection interface breidt de Iterable interface uit. Dit betekent dat het de eigenschappen en het gedrag van de Iterable interface erft en zijn eigen gedrag toevoegt voor het toevoegen, verwijderen en ophalen van elementen.

  3. Specifieke interfaces zoals List, Set en Queue breiden de Collection interface verder uit. Elke van deze interfaces heeft andere klassen die hun methoden implementeren. Bijvoorbeeld, ArrayList is een populaire implementatie van de List interface, HashSet implementeert de Set interface, enzovoort.

  4. De Map interface maakt deel uit van het Java Collections Framework, maar breidt de Collection interface niet uit, in tegenstelling tot de hierboven genoemde anderen.

  5. Alle interfaces en klassen in dit framework maken deel uit van het java.util pakket.

Opmerking: Een veelvoorkomende bron van verwarring in het Java Collections Framework draait om het verschil tussen Collection en Collections. Collection is een interface in het framework, terwijl Collections een utility-klasse is. De Collections-klasse biedt statische methoden die bewerkingen uitvoeren op de elementen van een collectie.

Java Collection Interfaces

U bent inmiddels bekend met de verschillende soorten collecties die de basis vormen van het collecties-framework. Nu zullen we een nadere blik werpen op de List, Set, Queue en Map interfaces.

In deze sectie bespreken we elk van deze interfaces terwijl we hun interne mechanismen verkennen. We zullen onderzoeken hoe ze omgaan met dubbele elementen en of ze de invoer van null-waarden ondersteunen. We begrijpen ook de volgorde van elementen tijdens invoer en hun ondersteuning voor synchronisatie, die betrekking heeft op het concept van threadveiligheid. Vervolgens zullen we een paar sleutelmethoden van deze interfaces doorlopen en afsluiten met een beoordeling van veelvoorkomende implementaties en hun prestaties voor verschillende bewerkingen.

Voordat we beginnen, laten we kort praten over Synchronisatie en Prestatie.

  • Synchronisatie beheert de toegang tot gedeelde objecten door meerdere threads, waardoor hun integriteit wordt gewaarborgd en conflicten worden voorkomen. Dit is cruciaal voor het handhaven van threadveiligheid.

  • Bij het kiezen van een collectie type is een belangrijke factor de prestaties tijdens gangbare operaties zoals invoegen, verwijderen en ophalen. Prestaties worden meestal uitgedrukt met behulp van Big-O notatie. Je kunt er meer over leren hier.

Lijsten

Een Lijst is een geordende of sequentiële verzameling van elementen. Het volgt zero-based indexering, waardoor de elementen kunnen worden ingevoegd, verwijderd of benaderd met behulp van hun indexpositie.

  1. Interne werking: Een Lijst wordt intern ondersteund door ofwel een array of een gekoppelde lijst, afhankelijk van het type implementatie. Bijvoorbeeld, een ArrayList gebruikt een array, terwijl een LinkedList intern een gekoppelde lijst gebruikt. Je kunt er meer over lezen hier. Een Lijst past zich dynamisch aan bij het toevoegen of verwijderen van elementen. De op indexering gebaseerde toegang maakt het een zeer efficiënte type verzameling.

  2. Duplicaten: Duplicaat-elementen zijn toegestaan in een List, wat betekent dat er meer dan één element in een List kan zijn met dezelfde waarde. Elke waarde kan worden opgehaald op basis van de index waarop deze is opgeslagen.

  3. Null: Null-waarden zijn ook toegestaan in een List. Aangezien duplicaten zijn toegestaan, kun je ook meerdere null-elementen hebben.

  4. Volgorde: Een List behoudt de volgorde van invoer, wat betekent dat de elementen worden opgeslagen in dezelfde volgorde als ze zijn toegevoegd. Dit is handig wanneer je elementen wilt ophalen in de exacte volgorde waarin ze zijn ingevoerd.

  5. Synchronisatie: Een List is standaard niet gesynchroniseerd, wat betekent dat het geen ingebouwde manier heeft om toegang door meerdere threads tegelijkertijd te verwerken.

  6. Belangrijke methoden: Hier zijn enkele belangrijke methoden van een List interface: add(E element), get(int index), set(int index, E element), remove(int index), en size(). Laten we kijken hoe we deze methoden kunnen gebruiken met een voorbeeldprogramma.

     import java.util.ArrayList;
     import java.util.List;
    
     public class ListExample {
         public static void main(String[] args) {
             // Maak een lijst
             List<String> list = new ArrayList<>();
    
             // add(E element)
             list.add("Appel");
             list.add("Banaan");
             list.add("Kers");
    
             // get(int index)
             String secondElement = list.get(1); // "Banaan"
    
             // set(int index, E element)
             list.set(1, "Blauwe bes");
    
             // remove(int index)
             list.remove(0); // Verwijdert "Appel"
    
             // size()
             int size = list.size(); // 2
    
             // Print de lijst
             System.out.println(list); // Uitvoer: [Blauwe bes, Kers]
    
             // Print de grootte van de lijst
             System.out.println(size); // Uitvoer: 2
         }
     }
    
  7. Veelvoorkomende implementaties: ArrayList, LinkedList, Vector, Stack

  8. Prestaties: Typisch zijn invoer- en verwijderbewerkingen snel in zowel ArrayList als LinkedList. Maar het ophalen van elementen kan traag zijn omdat je door de knooppunten moet navigeren.

Bewerking ArrayList LinkedList
Invoeren Snel aan het einde – O(1) gemiddeld, traag aan het begin of in het midden – O(n) Snel aan het begin of in het midden – O(1), traag aan het einde – O(n)
Verwijderen Snel aan het einde – O(1) gemiddeld, traag aan het begin of in het midden – O(n) Snel – O(1) als de positie bekend is
Ophalen Snel – O(1) voor willekeurige toegang Traag – O(n) voor willekeurige toegang, aangezien dit navigeren inhoudt

Verzamelingen

Een Set is een type collectie dat geen duplicaat elementen toestaat en het concept van een wiskundige verzameling vertegenwoordigt.

  1. Interne werking: Een Set wordt intern ondersteund door een HashMap. Afhankelijk van het type implementatie wordt het ondersteund door een HashMap, LinkedHashMap of een TreeMap. Ik heb een gedetailleerd artikel geschreven over hoe HashMap intern werkt hier. Zorg ervoor dat je het bekijkt.

  2. Dupliceer elementen: Aangezien een Set het concept van een wiskundige verzameling vertegenwoordigt, zijn dubbele elementen niet toegestaan. Dit zorgt ervoor dat alle elementen uniek zijn, waardoor de integriteit van de verzameling behouden blijft.

  3. Null: Een maximaal aantal van één null-waarde is toegestaan in een Set omdat duplicaten niet zijn toegestaan. Maar dit geldt niet voor de TreeSet implementatie, waar null-waarden helemaal niet zijn toegestaan.

  4. Ordening: De ordening van elementen in een Set hangt af van het type implementatie.

    • HashSet: De volgorde is niet gegarandeerd, en elementen kunnen op elke positie worden geplaatst.

    • LinkedHashSet: Deze implementatie behoudt de volgorde van invoer, zodat je de elementen in dezelfde volgorde kunt ophalen als waarin ze zijn ingevoerd.

    • TreeSet: Elementen worden ingevoegd op basis van hun natuurlijke volgorde. Alternatief kun je de invoervolgorde beheersen door een aangepaste comparator op te geven.

  5. Synchronisatie: Een Set is niet gesynchroniseerd, wat betekent dat je mogelijk tegen gelijktijdigheidsproblemen aanloopt, zoals racecondities, die de gegevensintegriteit kunnen beïnvloeden als twee of meer threads tegelijkertijd proberen toegang te krijgen tot een Set object.

  6. Belangrijke methoden: Hier zijn enkele belangrijke methoden van een Set interface: add(E element), remove(Object o), contains(Object o), en size(). Laten we kijken hoe we deze methoden kunnen gebruiken met een voorbeeldprogramma.

     import java.util.HashSet;
     import java.util.Set;
    
     public class SetExample {
         public static void main(String[] args) {
             // Maak een set
             Set<String> set = new HashSet<>();
    
             // Voeg elementen toe aan de set
             set.add("Apple");
             set.add("Banana");
             set.add("Cherry");
    
             // Verwijder een element uit de set
             set.remove("Banana");
    
             // Controleer of de set een element bevat
             boolean containsApple = set.contains("Apple");
             System.out.println("Bevat Apple: " + containsApple);
    
             // Krijg de grootte van de set
             int size = set.size();
             System.out.println("Grootte van de set: " + size);
         }
     }
    
  7. Veelvoorkomende implementaties: HashSet, LinkedHashSet, TreeSet

  8. Prestaties: Set implementaties bieden snelle prestaties voor basisbewerkingen, behalve voor een TreeSet, waar de prestaties relatief langzamer kunnen zijn omdat de interne datastructuur het sorteren van de elementen tijdens deze bewerkingen omvat.

Bewerking HashSet LinkedHashSet TreeSet
Invoegen Snel – O(1) Snel – O(1) Langzamer – O(log n)
Verwijderen Snel – O(1) Snel – O(1) Langzamer – O(log n)
Ophalen Snel – O(1) Snel – O(1) Langzamer – O(log n)

Queues

Een Queue is een lineaire verzameling van elementen die wordt gebruikt om meerdere items vast te houden voordat ze worden verwerkt, meestal volgens de FIFO (first-in-first-out) volgorde. Dit betekent dat elementen aan de ene kant worden toegevoegd en aan de andere kant worden verwijderd, zodat het eerste element dat aan de wachtrij is toegevoegd, het eerste is dat wordt verwijderd.

  1. Intern mechanisme: De interne werking van een Queue kan verschillen op basis van de specifieke implementatie.

    • LinkedList – gebruikt een dubbel gekoppelde lijst om elementen op te slaan, wat betekent dat je zowel vooruit als achteruit kunt doorlopen, wat flexibele bewerkingen mogelijk maakt.

    • PriorityQueue – wordt intern ondersteund door een binaire heap, wat zeer efficiënt is voor opvraagoperaties.

    • ArrayDeque – is geïmplementeerd met behulp van een array die uitbreidt of krimpt naarmate elementen worden toegevoegd of verwijderd. Hier kunnen elementen aan beide uiteinden van de wachtrij worden toegevoegd of verwijderd.

  2. Duplicates: In een Queue zijn dubbele elementen toegestaan, waardoor meerdere instanties van dezelfde waarde kunnen worden ingevoegd.

  3. Null: Je kunt geen null-waarde invoegen in een Queue omdat sommige methoden van een Queue ontworpen zijn om null terug te geven om aan te geven dat deze leeg is. Om verwarring te voorkomen, zijn null-waarden niet toegestaan.

  4. Ordening: Elementen worden ingevoegd op basis van hun natuurlijke volgorde. Als alternatief kunt u de invoegvolgorde regelen door een aangepaste vergelijker op te geven.

  5. Synchronisatie: Een Queue is standaard niet gesynchroniseerd. Maar u kunt een ConcurrentLinkedQueue of een BlockingQueue implementatie gebruiken voor het bereiken van threadveiligheid.

  6. Belangrijke methoden: Hier zijn enkele belangrijke methoden van een Queue interface: add(E element), offer(E element), poll(), en peek(). Laten we kijken hoe we deze methoden kunnen gebruiken met een voorbeeldprogramma.

     import java.util.LinkedList;
     import java.util.Queue;
    
     public class QueueExample {
         public static void main(String[] args) {
             // Maak een wachtrij met behulp van LinkedList
             Queue<String> queue = new LinkedList<>();
    
             // Gebruik de add-methode om elementen toe te voegen, gooit een uitzondering als de invoeging mislukt
             queue.add("Element1");
             queue.add("Element2");
             queue.add("Element3");
    
             // Gebruik de offer-methode om elementen toe te voegen, geeft false terug als de invoeging mislukt
             queue.offer("Element4");
    
             // Toon de wachtrij
             System.out.println("Wachtrij: " + queue);
    
             // Peek naar het eerste element (verwijdert het niet)
             String firstElement = queue.peek();
             System.out.println("Peek: " + firstElement); // geeft "Element1" terug
    
             // Poll het eerste element (haalt het op en verwijdert het)
             String polledElement = queue.poll();
             System.out.println("Poll: " + polledElement); // geeft "Element1" terug
    
             // Toon de wachtrij na de poll
             System.out.println("Wachtrij na poll: " + queue);
         }
     }
    
  7. Veelvoorkomende implementaties: LinkedList, PriorityQueue, ArrayDeque

  8. Prestaties: Implementaties zoals LinkedList en ArrayDeque zijn meestal snel voor het toevoegen en verwijderen van items. De PriorityQueue is iets langzamer omdat het items invoegt op basis van de ingestelde prioriteitsvolgorde.

Operatie LinkedList PriorityQueue ArrayDeque
Invoegen Snel aan het begin of in het midden – O(1), traag aan het einde – O(n) Trager – O(log n) Snel – O(1), Traag – O(n), als het resizing van de interne array inhoudt
Verwijderen Snel – O(1) als de positie bekend is Trager – O(log n) Snel – O(1), Traag – O(n), als het resizing van de interne array inhoudt
Ophalen Traag – O(n) voor willekeurige toegang, omdat het traverseren inhoudt Snel – O(1) Snel – O(1)

Kaarten

Een Map vertegenwoordigt een verzameling van sleutel-waarde paren, waarbij elke sleutel naar een enkele waarde wijst. Hoewel Map deel uitmaakt van het Java Collection framework, breidt het de java.util.Collection interface niet uit.

  1. Interne werking: Een Map werkt intern met behulp van een HashTable gebaseerd op het concept van hashing. Ik heb een gedetailleerd artikel over dit onderwerp geschreven, dus lees het voor een dieper begrip.

  2. Dupliceerwaarden: Een Map slaat gegevens op als sleutel-waarde paren. Hier is elke sleutel uniek, dus dubbele sleutels zijn niet toegestaan. Maar dubbele waarden zijn toegestaan.

  3. Null: Aangezien dubbele sleutels niet zijn toegestaan, kan een Map slechts één null-sleutel hebben. Aangezien dubbele waarden zijn toegestaan, kan het meerdere null-waarden hebben. In de TreeMap implementatie kunnen sleutels niet null zijn omdat het de elementen op basis van de sleutels sorteert. Null-waarden zijn echter toegestaan.

  4. Ordening: De volgorde van invoer van een Map varieert afhankelijk van de implementatie:

    • HashMap – de invoervolgorde is niet gegarandeerd, aangezien deze is gebaseerd op het concept van hashing.

    • LinkedHashMap – de invoervolgorde wordt behouden en je kunt de elementen in dezelfde volgorde terughalen als waarin ze aan de collectie zijn toegevoegd.

    • TreeMap – Elementen worden ingevoegd op basis van hun natuurlijke volgorde. Alternatief kun je de invoervolgorde beheersen door een aangepaste comparator op te geven.

  5. Synchronisatie: Een Map is standaard niet gesynchroniseerd. Maar je kunt Collections.synchronizedMap() of ConcurrentHashMap implementaties gebruiken om threadveiligheid te bereiken.

  6. Belangrijke methoden: Hier zijn enkele belangrijke methoden van een Map interface: put(K key, V value), get(Object key), remove(Object key), containsKey(Object key), en keySet(). Laten we kijken hoe we deze methoden kunnen gebruiken met een voorbeeldprogramma.

     import java.util.HashMap;
     import java.util.Map;
     import java.util.Set;
    
     public class MapMethodsExample {
         public static void main(String[] args) {
             // Maak een nieuwe HashMap aan
             Map<String, Integer> map = new HashMap<>();
    
             // put(K key, V value) - Voegt sleutel-waarde paren toe aan de map
             map.put("Apple", 1);
             map.put("Banana", 2);
             map.put("Orange", 3);
    
             // get(Object key) - Geeft de waarde terug die aan de sleutel is gekoppeld
             Integer value = map.get("Banana");
             System.out.println("Waarde voor 'Banana': " + value);
    
             // remove(Object key) - Verwijdert het sleutel-waarde paar voor de opgegeven sleutel
             map.remove("Orange");
    
             // containsKey(Object key) - Controleert of de map de opgegeven sleutel bevat
             boolean hasApple = map.containsKey("Apple");
             System.out.println("Bevat 'Apple': " + hasApple);
    
             // keySet() - Geeft een setweergave van de sleutels in de map terug
             Set<String> keys = map.keySet();
             System.out.println("Sleutels in map: " + keys);
         }
     }
    
  7. Veelvoorkomende implementaties: HashMap, LinkedHashMap, TreeMap, Hashtable, ConcurrentHashMap

  8. Prestaties: De HashMap implementatie wordt veel gebruikt, voornamelijk vanwege de efficiënte prestatiekenmerken zoals weergegeven in de onderstaande tabel.

Bewerking HashMap LinkedHashMap TreeMap
Invoegen Snel – O(1) Snel – O(1) Langzamer – O(log n)
Verwijderen Snel – O(1) Snel – O(1) Langzamer – O(log n)
Ophalen Snel – O(1) Snel – O(1) Langzamer – O(log n)

Collections Utility Class

Zoals aan het begin van dit artikel is benadrukt, heeft de Collections utility klasse verschillende nuttige statische methoden waarmee je veelgebruikte bewerkingen op de elementen van een collectie kunt uitvoeren. Deze methoden helpen je de boilerplate-code in je applicatie te verminderen en laten je focussen op de bedrijfslogica.

Hier zijn enkele belangrijke kenmerken en methoden, samen met wat ze doen, kort vermeld:

  1. Sorteer: Collections.sort(List<T>) – deze methode wordt gebruikt om de elementen van een lijst in oplopende volgorde te sorteren.

  2. Zoeken: Collections.binarySearch(List<T>, key) – deze methode wordt gebruikt om naar een specifiek element in een gesorteerde lijst te zoeken en de index ervan terug te geven.

  3. Omgekeerde volgorde: Collections.reverse(List<T>) – deze methode wordt gebruikt om de volgorde van elementen in een lijst om te keren.

  4. Min/Max Operaties: Collections.min(Collection<T>) en Collections.max(Collection<T>) – deze methoden worden gebruikt om respectievelijk de minimum- en maximumelementen in een collectie te vinden.

  5. Synchronisatie: Collections.synchronizedList(List<T>) – deze methode wordt gebruikt om een lijst thread-veilig te maken door deze te synchroniseren.

  6. Niet-wijzigbare Collecties: Collections.unmodifiableList(List<T>) – deze methode wordt gebruikt om een alleen-lezen weergave van een lijst te creëren, waardoor wijzigingen worden voorkomen.

Hier is een voorbeeld van een Java-programma dat verschillende functionaliteiten van de Collections utility klasse demonstreert:

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);

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

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

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

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

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

        // Niet-wijzigbare Collecties
        List<Integer> unmodifiableList = Collections.unmodifiableList(numbers);
        System.out.println("Unmodifiable List: " + unmodifiableList);
    }
}

Dit programma demonstreert sorteren, zoeken, omkeren, het vinden van minimum- en maximumwaarden, synchroniseren en het creëren van een niet-wijzigbare lijst met behulp van de Collections utility klasse.

Conclusie

In dit artikel heb je geleerd over het Java Collections Framework en hoe het helpt bij het beheren van groepen objecten in Java-toepassingen. We hebben verschillende soorten collecties verkend, zoals Lijsten, Sets, Queues en Maps, en inzicht gekregen in enkele van de belangrijkste kenmerken en hoe elk van deze types deze ondersteunt.

Je hebt geleerd over prestaties, synchronisatie en belangrijke methoden, en waardevolle inzichten opgedaan in het kiezen van de juiste datastructuren voor jouw behoeften.

Door deze concepten te begrijpen, kun je het Java Collections Framework volledig benutten, waardoor je efficiëntere code kunt schrijven en robuuste applicaties kunt bouwen.

Als je dit artikel interessant vond, voel je vrij om mijn andere artikelen op freeCodeCamp te bekijken en met me te verbinden op LinkedIn.