Panoramica
In questo rapido tutorial, parleremo di quattro modi diversi per rimuovere elementi dalle collezioni Java che corrispondono a certi predicati.
Vedremo naturalmente anche alcuni avvertimenti.
Definire la nostra collezione
Prima, illustreremo due approcci che mutano la struttura dati originale. Poi parleremo di altre due opzioni che, invece di rimuovere gli elementi, creeranno una copia della Collection originale senza di essi.
Utilizziamo la seguente collezione nel corso dei nostri esempi per dimostrare come possiamo ottenere lo stesso risultato usando metodi diversi:
Collection<String> names = new ArrayList<>();names.add("John");names.add("Ana");names.add("Mary");names.add("Anthony");names.add("Mark");
Rimozione di elementi con Iterator
L’Iterator di Java ci permette sia di camminare che di rimuovere ogni singolo elemento all’interno di una Collection.
Per farlo, dobbiamo prima recuperare un iteratore sui suoi elementi usando il metodo iterator. In seguito, possiamo visitare ogni elemento con l’aiuto di next e rimuoverlo usando remove:
Iterator<String> i = names.iterator();while(i.hasNext()) { String e = i.next(); if (e.startsWith("A")) { i.remove(); }}
Nonostante la sua semplicità, ci sono alcune avvertenze che dovremmo considerare:
- A seconda della collezione potremmo incorrere in eccezioni ConcurrentModificationException
- Dobbiamo iterare sopra gli elementi prima di poterli rimuovere
- A seconda della collezione, remove potrebbe comportarsi diversamente da quanto previsto. Per esempio: ArrayList.Iterator rimuove l’elemento dalla collezione e sposta i dati successivi a sinistra, mentre LinkedList.Iterator regola semplicemente il puntatore all’elemento successivo. Come tale, LinkedList.Iterator funziona molto meglio di ArrayList.Iterator quando si rimuovono elementi
Java 8 e Collection.removeIf()
Java 8 ha introdotto un nuovo metodo nell’interfaccia Collection che fornisce un modo più conciso per rimuovere elementi usando Predicate:
names.removeIf(e -> e.startsWith("A"));
È importante notare che al contrario dell’approccio Iterator, removeIf funziona altrettanto bene sia in LinkedList che in ArrayList.
In Java 8, ArrayList sovrascrive l’implementazione di default – che si basa su Iterator – e implementa una strategia diversa: prima, itera sugli elementi e contrassegna quelli che corrispondono al nostro predicato; in seguito, itera una seconda volta per rimuovere (e spostare) gli elementi che sono stati marcati nella prima iterazione.
Java 8 e l’introduzione degli Stream
Una delle nuove caratteristiche principali di Java 8 è stata l’aggiunta degli Stream (e dei Collettori). Ci sono molti modi per creare uno Stream da una sorgente. Tuttavia, la maggior parte delle operazioni che riguardano l’istanza di Stream non mutano la sua sorgente, piuttosto, l’API si concentra sulla creazione di copie di una sorgente e sull’esecuzione di qualsiasi operazione di cui potremmo aver bisogno.
Diamo un’occhiata a come possiamo usare Stream e Collectors per trovare/filtrare elementi che corrispondono, e non corrispondono, al nostro Predicate.
5.1. Rimuovere elementi con Stream
Rimuovere, o meglio, filtrare elementi usando Stream è abbastanza semplice, abbiamo solo bisogno di creare un’istanza di Stream usando la nostra Collection, invocare il filtro con il nostro Predicate e poi raccogliere il risultato con l’aiuto di Collectors:
Collection<String> filteredCollection = names .stream() .filter(e -> !e.startsWith("A")) .collect(Collectors.toList());
Streaming è meno invasivo degli approcci precedenti, promuove l’isolamento e permette la creazione di più copie dalla stessa fonte. Tuttavia, dobbiamo tenere presente che aumenta anche la memoria utilizzata dalla nostra applicazione.
5.2. Collectors.partitioningBy
Combinare sia Stream.filter che Collectors è abbastanza comodo, anche se potremmo imbatterci in scenari in cui abbiamo bisogno di elementi corrispondenti e non corrispondenti. In questi casi possiamo approfittare di 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));
Questo metodo restituisce una mappa che contiene solo due chiavi, true e false, ognuna delle quali punta ad una lista che contiene gli elementi corrispondenti e non corrispondenti, rispettivamente.
Conclusione
In questo articolo, abbiamo visto alcuni metodi per rimuovere elementi dalle collezioni e alcuni dei loro limiti.