1. Introduzione
Nella programmazione ci troviamo continuamente nella situazione in cui, da un grande insieme di dati, occorre selezionare solo gli elementi necessari — questo è il filtraggio. Volete mantenere solo i numeri pari, trovare le stringhe che contengono la parola "Java", oppure selezionare gli utenti con più di 18 anni — tutto ciò sono compiti di filtraggio.
In Java operazioni di questo tipo sono ovunque; è quindi importante padroneggiare con sicurezza i diversi modi di eseguirle e capirne le peculiarità.
2. Filtraggio con il ciclo
Cominciamo dal metodo più basilare — il normale ciclo for. Questo approccio è detto imperativo perché si specifica esplicitamente cosa fare e come farlo. È ben leggibile e semplice da comprendere.
Esempio: mantenere solo i numeri pari
import java.util.*;
public class FilterExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenNumbers = new ArrayList<>(); // Creiamo una nuova lista per il risultato
for (Integer n : numbers) {
if (n % 2 == 0) { // Verifichiamo la condizione: numero pari
evenNumbers.add(n);
}
}
System.out.println("Numeri pari: " + evenNumbers);
}
}
Risultato:
Numeri pari: [2, 4, 6, 8, 10]
È importante ricordare che la collezione originaria (numbers) non cambia. Formiamo una nuova lista risultato.
Esempio: filtrare le stringhe per sottostringa
List<String> words = Arrays.asList("java", "python", "javascript", "kotlin", "c++");
List<String> javaWords = new ArrayList<>();
for (String word : words) {
if (word.contains("java")) {
javaWords.add(word);
}
}
System.out.println(javaWords); // [java, javascript]
Stesso principio: percorriamo la lista di stringhe e verifichiamo la presenza della sottostringa con il metodo contains.
3. Rimozione di elementi da una collezione: perché non è così semplice?
Qual è l'insidia?
A volte si vuole eliminare dalla collezione originaria tutti gli elementi superflui. Ma se si prova a farlo durante l'iterazione in un ciclo for-each, si ottiene l'eccezione ConcurrentModificationException.
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, -2, 3, -4, 5));
for (Integer n : numbers) {
if (n < 0) {
numbers.remove(n); // PERICOLOSO! ConcurrentModificationException!
}
}
Risultato:
Exception in thread "main" java.util.ConcurrentModificationException
Le collezioni «non gradiscono» essere modificate durante questo tipo di iterazione — l'iterazione si interrompe.
Come rimuovere correttamente elementi da una collezione?
Metodo 1: usare Iterator.remove()
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, -2, 3, -4, 5));
Iterator<Integer> it = numbers.iterator();
while (it.hasNext()) {
Integer n = it.next();
if (n < 0) {
it.remove(); // Rimozione sicura!
}
}
System.out.println(numbers); // [1, 3, 5]
Qui creiamo un iteratore con il metodo iterator(), percorriamo la collezione tramite hasNext() e next(), e rimuoviamo gli elementi indesiderati chiamando it.remove().
Metodo 2: creare una nuova lista solo con gli elementi necessari
List<Integer> numbers = Arrays.asList(1, -2, 3, -4, 5);
List<Integer> positive = new ArrayList<>();
for (Integer n : numbers) {
if (n >= 0) {
positive.add(n);
}
}
System.out.println(positive); // [1, 3, 5]
Non tocchiamo la collezione originaria e costruiamo una nuova lista. L'approccio è sicuro, chiaro e si estende facilmente al crescere della complessità delle condizioni.
Metodo 3: usare removeIf (Java 8+)
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, -2, 3, -4, 5));
numbers.removeIf(n -> n < 0);
System.out.println(numbers); // [1, 3, 5]
Con una sola riga passiamo la condizione: «rimuovi tutto ciò che è minore di zero». I dettagli interni della modifica sicura sono gestiti dalla collezione.
In sintesi: per rimuovere elementi non utilizzare for-each. Scegli tra Iterator.remove(), la creazione di una nuova lista o il conciso removeIf.
4. Quando usare quale metodo per il filtraggio?
L'approccio imperativo tramite un ciclo normale è comodo quando non solo devi selezionare gli elementi, ma vuoi anche farci subito qualcosa (ad esempio stamparli o trasformarli). È semplice e trasparente.
Se si tratta proprio di rimuovere dalla collezione originaria, evita di farlo dentro for-each. Usa Iterator.remove() per un controllo passo passo oppure il moderno removeIf() — il modo più breve ed espressivo.
Esempio: c'è una lista di parole, bisogna eliminare tutte le parole più corte di quattro caratteri:
List<String> words = new ArrayList<>(Arrays.asList("Java", "is", "fun", "awesome", "code"));
words.removeIf(word -> word.length() < 4);
System.out.println(words); // [Java, awesome, code]
Il metodo removeIf accetta un predicato — una regola di filtraggio — e rimuove tutto ciò che vi corrisponde.
5. Errori tipici nel filtraggio delle collezioni
Errore n. 1: tentare di rimuovere elementi dalla collezione in un ciclo for-each.
Un codice del genere porterà a ConcurrentModificationException:
for (Integer n : numbers) {
if (n < 0) {
numbers.remove(n); // BOOM! ConcurrentModificationException
}
}
Alternative corrette: usa Iterator.remove() oppure removeIf.
Errore n. 2: condizione di filtraggio formulata in modo errato.
Bisogna rimuovere solo i numeri negativi:
List<Integer> numbers = new ArrayList<>(Arrays.asList(-3, -1, 0, 2, 4));
numbers.removeIf(n -> n < 0);
System.out.println(numbers); // [0, 2, 4]
Ma se per errore si scrivono i «non positivi» e si usa <=, sparirà anche lo zero:
numbers.removeIf(n -> n <= 0); // Risultato: [2, 4] — lo zero è stato rimosso per errore
Presta attenzione alla precisione delle condizioni: un solo confronto errato cambia completamente il risultato.
GO TO FULL VERSION