1. Введение
В программировании мы постоянно сталкиваемся с ситуацией, когда из большого набора данных нужно выбрать только нужные элементы — это и есть фильтрация. Хотите оставить только чётные числа, найти строки, содержащие слово "Java", или выбрать пользователей старше 18 лет — всё это задачи фильтрации.
В Java подобные операции встречаются на каждом шагу, поэтому важно уверенно владеть различными способами их выполнения и понимать их особенности.
2. Фильтрация с помощью цикла
Начнём с самого базового способа — обычный цикл for. Такой подход называют императивным, потому что вы явно указываете, что и как делать. Он хорошо читается и прост для понимания.
Пример: оставить только чётные числа
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<>(); // Создаём новый список для результата
for (Integer n : numbers) {
if (n % 2 == 0) { // Проверяем условие: чётное число
evenNumbers.add(n);
}
}
System.out.println("Чётные числа: " + evenNumbers);
}
}
Результат:
Чётные числа: [2, 4, 6, 8, 10]
Здесь важно помнить: исходная коллекция (numbers) не меняется. Мы формируем новый список-результат.
Пример: фильтрация строк по подстроке
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]
Тот же принцип: пробегаемся по списку строк и проверяем наличие подстроки с помощью метода contains.
3. Удаление элементов из коллекции: почему не всё так просто?
В чём подвох?
Иногда хочется удалить из исходной коллекции все ненужные элементы. Но если попытаться сделать это во время перебора в цикле for-each, вы получите исключение ConcurrentModificationException.
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, -2, 3, -4, 5));
for (Integer n : numbers) {
if (n < 0) {
numbers.remove(n); // ОПАСНО! ConcurrentModificationException!
}
}
Результат:
Exception in thread "main" java.util.ConcurrentModificationException
Коллекции «не любят», когда их модифицируют во время такого перебора — итерация ломается.
Как правильно удалять элементы из коллекции?
Способ 1: использовать 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(); // Безопасно удаляем!
}
}
System.out.println(numbers); // [1, 3, 5]
Здесь мы создаём итератор методом iterator(), двигаемся по коллекции через hasNext() и next(), а ненужные элементы удаляем вызовом it.remove().
Способ 2: создать новый список только с нужными элементами
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]
Мы не трогаем исходную коллекцию и собираем новый список. Подход безопасный, наглядный и легко расширяется при усложнении условий.
Способ 3: использовать 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]
Одной строкой передаём условие: «удали всё, что меньше нуля». Внутренние детали безопасной модификации берёт на себя коллекция.
Итого: для удаления элементов не используйте for-each. Выбирайте между Iterator.remove(), созданием нового списка или лаконичным removeIf.
4. Когда какой способ использовать для фильтрации?
Императивный подход через обычный цикл удобен, когда вам нужно не только отобрать элементы, но и сразу что-то с ними сделать (например, вывести или преобразовать). Он прост и прозрачен.
Если речь идёт именно об удалении из исходной коллекции, избегайте удаления в for-each. Используйте Iterator.remove() для пошагового контроля или современный removeIf() — максимально короткий и выразительный способ.
Пример: есть список слов, нужно удалить все слова короче четырёх символов:
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]
Метод removeIf принимает предикат — правило фильтрации — и удаляет всё, что ему соответствует.
5. Типичные ошибки при фильтрации коллекций
Ошибка №1: попытка удалить элементы из коллекции в цикле for-each.
Такой код приведёт к ConcurrentModificationException:
for (Integer n : numbers) {
if (n < 0) {
numbers.remove(n); // БУМ! ConcurrentModificationException
}
}
Правильные варианты: используйте Iterator.remove() или removeIf.
Ошибка №2: неверно сформулированное условие фильтрации.
Нужно удалить только отрицательные числа:
List<Integer> numbers = new ArrayList<>(Arrays.asList(-3, -1, 0, 2, 4));
numbers.removeIf(n -> n < 0);
System.out.println(numbers); // [0, 2, 4]
Но если по ошибке написать «неположительные» и использовать <=, то исчезнет и ноль:
numbers.removeIf(n -> n <= 0); // Результат: [2, 4] — ноль удалён по ошибке
Следите за точностью условий: одно неверное сравнение полностью меняет результат.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ