Overview
In deze korte tutorial gaan we het hebben over vier verschillende manieren om items uit Java Collections te verwijderen die aan bepaalde predicaten voldoen.
We zullen natuurlijk ook kijken naar enkele van de voorbehouden.
Defining Our Collection
Eerst gaan we twee benaderingen illustreren die de oorspronkelijke datastructuur muteren. Daarna zullen we het hebben over twee andere opties die in plaats van de items te verwijderen, een kopie van de oorspronkelijke Collection zullen maken zonder hen.
Laten we de volgende collection gebruiken in onze voorbeelden om te demonstreren hoe we hetzelfde resultaat kunnen bereiken met verschillende methoden:
Collection<String> names = new ArrayList<>();names.add("John");names.add("Ana");names.add("Mary");names.add("Anthony");names.add("Mark");
Elementen verwijderen met Iterator
Java’s Iterator stelt ons in staat om zowel te lopen als elk individueel element binnen een Collection te verwijderen.
Daartoe moeten we eerst een iterator over de elementen ophalen met behulp van de iterator-methode. Daarna kunnen we elk element bezoeken met behulp van next en ze verwijderen met remove:
Iterator<String> i = names.iterator();while(i.hasNext()) { String e = i.next(); if (e.startsWith("A")) { i.remove(); }}
Ondanks de eenvoud zijn er enkele kanttekeningen waar we rekening mee moeten houden:
- Afhankelijk van de collectie kunnen we ConcurrentModificationException exceptions tegenkomen
- We moeten eerst over de elementen itereren voordat we ze kunnen verwijderen
- Afhankelijk van de collectie kan remove zich anders gedragen dan verwacht. Bijv: ArrayList.Iterator verwijdert het element uit de collectie en verschuift de volgende gegevens naar links terwijl, LinkedList.Iterator gewoon de pointer naar het volgende element aanpast. LinkedList.Iterator presteert dus veel beter dan ArrayList.Iterator bij het verwijderen van items
Java 8 en Collection.removeIf()
Java 8 introduceerde een nieuwe methode in de Collection interface die een beknoptere manier biedt om elementen te verwijderen met behulp van Predicate:
names.removeIf(e -> e.startsWith("A"));
Het is belangrijk om op te merken dat in tegenstelling tot de Iterator aanpak, removeIf vergelijkbaar goed presteert in zowel LinkedList als ArrayList.
In Java 8, ArrayList overschrijft de standaard implementatie – die vertrouwt op Iterator – en implementeert een andere strategie: eerst, iterates over de elementen en markeert degenen die overeenkomen met onze Predicate; daarna, iterates een tweede keer te verwijderen (en verschuiven) van de elementen die werden gemarkeerd in de eerste iteratie.
Java 8 en de introductie van Stream
Eén van de belangrijkste nieuwe functies in Java 8 was de toevoeging van Stream (en Collectors). Er zijn vele manieren om een Stream van een bron te maken. De meeste bewerkingen op de Stream instantie zullen echter de bron niet muteren, maar de API richt zich op het maken van kopieën van een bron en het uitvoeren van elke bewerking die we daarin nodig hebben.
Laten we eens kijken hoe we Stream en Collectors kunnen gebruiken om elementen te vinden/filteren die wel en niet overeenkomen met ons Predicaat.
5.1. Elementen verwijderen met Stream
Het verwijderen, of beter gezegd, filteren van elementen met behulp van Stream is vrij eenvoudig, we hoeven alleen maar een instantie van Stream te maken met behulp van onze Collection, filter aan te roepen met onze Predicate en vervolgens het resultaat te verzamelen met behulp van Collectors:
Collection<String> filteredCollection = names .stream() .filter(e -> !e.startsWith("A")) .collect(Collectors.toList());
Streaming is minder ingrijpend dan de vorige benaderingen, het bevordert de isolatie en maakt het mogelijk om meerdere kopieën te maken van dezelfde bron. We moeten echter in gedachten houden dat het ook het geheugen verhoogt dat door onze toepassing wordt gebruikt.
5.2. Collectors.partitioningBy
Het combineren van zowel Stream.filter als Collectors is best handig, hoewel we in scenario’s terecht kunnen komen waarin we zowel overeenkomende als niet-overeenkomende elementen nodig hebben. In dergelijke gevallen kunnen we gebruik maken van Collectors.partitioningBy:
Map<Boolean, List<String>> classifiedElements = names .stream() .collect(Collectors.partitioningBy((String e) -> !e.startsWith("A")));String matching = String.join(",", classifiedElements.get(true));String nonMatching = String.join(",", classifiedElements.get(false));
Deze methode retourneert een Map die slechts twee sleutels bevat, true en false, die elk naar een lijst wijzen die respectievelijk de overeenkomende en niet-overeenkomende elementen bevat.
Conclusie
In dit artikel hebben we gekeken naar enkele methoden om elementen uit Collecties te verwijderen en enkele van hun voorbehouden.