1. Wprowadzenie
W programowaniu często spotykamy się z sytuacją, gdy z dużego zbioru danych trzeba wybrać tylko potrzebne elementy — to właśnie jest filtrowanie. Chcesz zostawić tylko liczby parzyste, znaleźć łańcuchy zawierające słowo "Java" lub wybrać użytkowników starszych niż 18 lat — to wszystko są zadania filtrowania.
W Java takie operacje pojawiają się na każdym kroku, dlatego ważne jest, aby pewnie posługiwać się różnymi sposobami ich wykonania i rozumieć ich specyfikę.
2. Filtrowanie za pomocą pętli
Zacznijmy od najbardziej podstawowego sposobu — zwykła pętla for. Takie podejście nazywa się imperatywnym, ponieważ jawnie wskazujesz, co i jak zrobić. Jest czytelne i łatwe do zrozumienia.
Przykład: zostawić tylko liczby parzyste
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<>(); // Tworzymy nową listę na wynik
for (Integer n : numbers) {
if (n % 2 == 0) { // Sprawdzamy warunek: liczba parzysta
evenNumbers.add(n);
}
}
System.out.println("Liczby parzyste: " + evenNumbers);
}
}
Wynik:
Liczby parzyste: [2, 4, 6, 8, 10]
Ważne: kolekcja źródłowa (numbers) nie ulega zmianie. Tworzymy nową listę z wynikiem.
Przykład: filtrowanie łańcuchów po podłańcuchu
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]
Ten sam princip: przechodzimy po liście łańcuchów i sprawdzamy obecność podłańcucha metodą contains.
3. Usuwanie elementów z kolekcji: dlaczego to nie zawsze takie proste?
Gdzie jest haczyk?
Czasem chcemy usunąć z kolekcji źródłowej wszystkie niepotrzebne elementy. Jednak jeśli spróbujesz zrobić to podczas iteracji w pętli for-each, otrzymasz wyjątek ConcurrentModificationException.
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, -2, 3, -4, 5));
for (Integer n : numbers) {
if (n < 0) {
numbers.remove(n); // NIEBEZPIECZNE! ConcurrentModificationException!
}
}
Wynik:
Exception in thread "main" java.util.ConcurrentModificationException
Kolekcje „nie lubią”, gdy modyfikuje się je w trakcie takiej iteracji — iteracja się psuje.
Jak prawidłowo usuwać elementy z kolekcji?
Sposób 1: użyć 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(); // Usuwamy bezpiecznie!
}
}
System.out.println(numbers); // [1, 3, 5]
Tutaj tworzymy iterator metodą iterator(), poruszamy się po kolekcji przez hasNext() i next(), a zbędne elementy usuwamy wywołaniem it.remove().
Sposób 2: utworzyć nową listę tylko z potrzebnymi elementami
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]
Nie ruszamy kolekcji źródłowej, tylko zbieramy nową listę. Podejście jest bezpieczne, przejrzyste i łatwo je rozszerzać przy komplikowaniu warunków.
Sposób 3: użyć 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]
Jednym wyrażeniem przekazujemy warunek: „usuń wszystko, co mniejsze od zera”. Wewnętrzne szczegóły bezpiecznej modyfikacji bierze na siebie kolekcja.
Podsumowując: do usuwania elementów nie używaj for-each. Wybieraj między Iterator.remove(), utworzeniem nowej listy lub zwięzłym removeIf.
4. Kiedy używać którego sposobu filtrowania?
Podejście imperatywne przez zwykłą pętlę jest wygodne, gdy trzeba nie tylko wybrać elementy, ale od razu coś z nimi zrobić (np. wypisać lub przekształcić). Jest proste i przejrzyste.
Jeśli chodzi właśnie o usuwanie z kolekcji źródłowej, unikaj usuwania w for-each. Używaj Iterator.remove() do krokowej kontroli albo nowoczesnego removeIf() — maksymalnie krótkiego i wyrazistego sposobu.
Przykład: jest lista słów, trzeba usunąć wszystkie słowa krótsze niż cztery znaki:
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]
Metoda removeIf przyjmuje predykat — regułę filtrowania — i usuwa wszystko, co ją spełnia.
5. Typowe błędy przy filtrowaniu kolekcji
Błąd nr 1: próba usunięcia elementów z kolekcji w pętli for-each.
Taki kod doprowadzi do ConcurrentModificationException:
for (Integer n : numbers) {
if (n < 0) {
numbers.remove(n); // BUM! ConcurrentModificationException
}
}
Prawidłowe warianty: użyj Iterator.remove() lub removeIf.
Błąd nr 2: błędnie sformułowany warunek filtrowania.
Trzeba usunąć tylko liczby ujemne:
List<Integer> numbers = new ArrayList<>(Arrays.asList(-3, -1, 0, 2, 4));
numbers.removeIf(n -> n < 0);
System.out.println(numbers); // [0, 2, 4]
Ale jeśli przez pomyłkę napiszesz „niedodatnie” i użyjesz <=, zniknie też zero:
numbers.removeIf(n -> n <= 0); // Wynik: [2, 4] - zero usunięto przez pomyłkę
Dbaj o precyzję warunków: jedno błędne porównanie całkowicie zmienia wynik.
GO TO FULL VERSION