Фильтрация Java Stream на 1 элемент: обработка исключений
Быстрый ответ: код прямо здесь и сейчас
Если требуется строго одно значение, используйте следующую конструкцию:
Optional<T> unique = stream
.filter(condition)
.reduce((a, b) -> { throw new IllegalStateException("Обнаружено больше одного элемента!"); });
Переменная unique
будет содержать необходимый элемент или сгенерирует IllegalStateException, если будут обнаружены дубликаты.
Преобразуем Stream в List: Collectors.collectingAndThen
Вооружитесь Collectors.collectingAndThen
. Он позволяет связать возврат коллекции и дополнительные операции обработки:
T unique = stream
.filter(condition)
.collect(Collectors.collectingAndThen(Collectors.toList(), list -> {
if (list.size() == 1) {
return list.get(0);
} else if (list.isEmpty()) {
throw new NoSuchElementException("Элемент не найден!");
} else {
throw new IllegalStateException("Обнаружено больше одного элемента!");
}
}));
С помощью такого подхода вы получите точный результирующий элемент или исключение в случае возникновения ошибки.
Не теряемся: Atomic References
В многопоточном программировании используйте AtomicReference
в сочетании с forEach
для сохранения уникальности элемента:
AtomicReference<T> atomicReference = new AtomicReference<>();
stream.filter(condition)
.forEach(item -> {
if (!atomicReference.compareAndSet(null, item)) {
throw new IllegalStateException("Обнаружено больше одного элемента!"); // Найдены дополнительные!
}
});
T unique = atomicReference.get();
Таким образом, даже в многопоточной среде будет сохранён только один элемент.
Создаем свои собиратели
Для повышения производительности вы можете создать собственные сборщики:
public static <T> Collector<T, ?, T> toSingleton() {
return Collector.of(
AtomicReference::new,
(ref, item) -> {
if (!ref.compareAndSet(null, item)) {
throw new IllegalStateException("Обнаружено больше одного элемента!");
}
},
(ref1, ref2) -> {
if (ref1.get() != null && ref2.get() != null) {
throw new IllegalStateException("Обнаружено несколько элементов!");
}
return ref1.get() == null ? ref2 : ref1;
},
AtomicReference::get,
Collector.Characteristics.UNORDERED
);
}
Применяйте ваш сборщик так:
T unique = stream
.filter(condition)
.collect(toSingleton());
Самый короткий код с помощью Google Guava: MoreCollectors.onlyElement
Библиотека Google Guava предлагает удобный сборщик MoreCollectors.onlyElement
:
import static com.google.common.collect.MoreCollectors.onlyElement;
T unique = stream
.filter(condition)
.collect(onlyElement());
При его использовании выборка единственного элемента либо будет успешной, либо сгенерирует исключение IllegalArgumentException
.
Пример использования Guava: обработка исключений
При работе с методом onlyElement
от Guava будьте готовы к обработке исключений:
try {
T unique = stream
.filter(condition)
.collect(onlyElement());
} catch(IllegalArgumentException e) {
// Здесь реализован код обнаружения нескольких элементов
} catch(NoSuchElementException e) {
// Код для обработки ситуации, когда элемент не был найден
}
Перехватывайте эти исключения для обработки ошибок и поддержания целостности данных.
Визуализация
Визуализируем работу с Java Stream:
Java Stream (🌊): [🎩, 🎩, 🎩, 🎩, 🎩]
Метод .filter()
действует как отборочная комиссия:
stream.filter(guest -> guest.hasInvitation()).findFirst();
В результате получаем "звездного гостя":
VIP Lounge (🍾): [🎩]
Если подходящих гостей нет или их более чем нужно:
Party Over (🚫): Exception("Ожидался один VIP, найден " + VIPCount)
Только один VIP, либо вечеринка окончена!
Сокращаем, улучшаем, модифицируем: минимизируем дублирование кода
Чтобы избежать разочарования, добавьте эти методы в утилитный класс. Ваше будущее "я" будет вам благодарно за это. Создание собственных сборщиков также может стать вашей "фирменной" техникой при работе с большими данными. Не забывайте заблаговременно проверять состояние Stream.
Обсуждаем типы Holder
Иногда создание специального типа Holder может быть удобнее, чем использование AtomicReference
:
class Holder<T> {
private T value;
private boolean isSet = false;
public void set(T val) {
if (!isSet) {
value = val;
isSet = true;
} else {
throw new IllegalStateException("Значение уже установлено!"); // Проблема!
}
}
public T get() {
if (isSet) {
return value;
} else {
throw new NoSuchElementException("Значение не найдено!"); // Ошибка!
}
}
}
Таким образом, вы присваиваете имя "безымянному" объекту.
Привлекаем альтернативные методы работы со Stream:
.findFirst()
: возвращает один элемент вOptional
..limit(1)
: останавливается после встречи первого элемента.- Optional: оборачивает существование и концепцию "ноль или один" элемент в удобную структуру, часто используя
.reduce()
.
Полезные материалы
- Stream (Java Platform SE 8) — официальная документация Java 8 по Stream API.
- [Effective Java, 3rd Edition [Книга]](https://www.oreilly.com/library/view/effective-java-3rd/9780134686097/) — рекомендации по лучшим практикам, включая работу с потоками, от Джошуа Блоха.
- Java 8 Streams: несколько фильтров против сложного условия – Stack Overflow — обсуждение применения нескольких условий фильтрации.
- findAny и findFirst в Java Streams — различия и применения
findAny()
иfindFirst()
. - Java Stream API: примеры метода collect() — примеры использования
collect
. - Шпаргалка по Optional в Java 8 — объяснение и примеры использования
Optional
в Java.