Я пытаюсь использовать Java 8 Stream
s, чтобы найти элементы в LinkedList
. Однако я хочу гарантировать, что существует одно и только одно соответствие критериям фильтра.
Возьми этот код:
public static void main(String[] args) {
LinkedList<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));
User match = users.stream().filter((user) -> user.getId() == 1).findAny().get();
System.out.println(match.toString());
}
static class User {
@Override
public String toString() {
return id + " - " + username;
}
int id;
String username;
public User() {
}
public User(int id, String username) {
this.id = id;
this.username = username;
}
public void setUsername(String username) {
this.username = username;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public int getId() {
return id;
}
}
Этот код находит на User
основе их идентификатора. Но нет никаких гарантий, сколько User
s соответствует фильтру.
Изменение строки фильтра на:
User match = users.stream().filter((user) -> user.getId() < 0).findAny().get();
Будет бросать NoSuchElementException
(хорошо!)
Я хотел бы, чтобы он выдавал ошибку, если есть несколько совпадений. Есть ли способ сделать это?
java
lambda
java-8
java-stream
ryvantage
источник
источник
count()
это терминальная операция, поэтому вы не можете этого сделать. Поток не может быть использован после.Stream::size
?Stream
гораздо больше, чем я делал раньше ...LinkedHashSet
(при условии, что вы хотите сохранить порядок вставки) илиHashSet
все время. Если ваша коллекция используется только для поиска одного идентификатора пользователя, то почему вы собираете все остальные предметы? Если есть вероятность, что вам всегда нужно будет найти какой-то идентификатор пользователя, который также должен быть уникальным, тогда зачем использовать список, а не набор? Вы программируете в обратном направлении. Используйте правильную коллекцию для работы иОтветы:
Создать кастом
Collector
Мы используем
Collectors.collectingAndThen
для построения нашего желаемогоCollector
путемList
сCollectors.toList()
коллектором.IllegalStateException
iflist.size != 1
.Используется в качестве:
Затем вы можете настроить его
Collector
так, как вам нужно, например, дать исключение в качестве аргумента в конструкторе, настроить его, чтобы разрешить два значения, и даже больше.Альтернативное - возможно, менее элегантное - решение:
Вы можете использовать «обходной путь», который включает
peek()
иAtomicInteger
, но на самом деле вы не должны использовать это.То, что вы могли бы сделать, это просто собрать его в виде
List
, например так:источник
Iterables.getOnlyElement
сократил бы эти решения и предоставил бы лучшие сообщения об ошибках. Так же, как совет для других читателей, которые уже используют Google Guava.singletonCollector()
определения, устаревшую версией, которая осталась в посте, и переименовав ее вtoSingleton()
. Мои знания о Java-потоке немного устарели, но переименование выглядит для меня полезным. Просмотр этого изменения занял у меня 2 минуты, топы. Если у вас нет времени для просмотра изменений, могу ли я предложить вам попросить кого-нибудь сделать это в будущем, возможно, в чате Java ?Для полноты изложения приведем «однострочник», соответствующий превосходному ответу @ prunge:
Это получает единственный соответствующий элемент из потока, бросая
NoSuchElementException
если поток пуст илиIllegalStateException
в случае, если поток содержит более одного совпадающего элемента.Вариант этого подхода позволяет избежать преждевременного выброса исключения и вместо этого представляет результат в виде
Optional
содержащего либо единственный элемент, либо ничего (пустое), если имеется ноль или несколько элементов:источник
get()
доorElseThrow()
Другие ответы, которые включают в себя написание обычая
Collector
, вероятно, более эффективны (например, Луи Вассермана , +1), но если вы хотите краткости, я бы предложил следующее:Затем проверьте размер списка результатов.
источник
limit(2)
в этом решении? Какая разница, будет ли результирующий список 2 или 100? Если оно больше 1.Collectors.collectingAndThen(toList(), l -> { if (l.size() == 1) return l.get(0); throw new RuntimeException(); })
maxSize: the number of elements the stream should be limited to
. Так не должно ли быть.limit(1)
вместо.limit(2)
?result.size()
чтобы убедиться, что он равен 1. Если это 2, то есть более одного совпадения, так что это ошибка. Если бы вместо этого выполнялся кодlimit(1)
, более чем одно совпадение привело бы к одному элементу, который нельзя отличить от того, что было точно одно совпадение. Это пропустит ошибку, если OP обеспокоен.Гуава обеспечивает,
MoreCollectors.onlyElement()
что делает правильные вещи здесь. Но если вам придется сделать это самостоятельно, вы можете сделать это самостоятельноCollector
:... или использовать свой собственный
Holder
тип вместоAtomicReference
. Вы можете использовать этоCollector
сколько угодно.источник
Collector
был путь.List
обходится дороже, чем одна изменяемая ссылка.MoreCollectors.onlyElement()
должно быть первым (и, возможно, единственным :))Используйте гуавы в
MoreCollectors.onlyElement()
( JavaDoc ).Он делает то, что вы хотите, и выдает,
IllegalArgumentException
если поток состоит из двух или более элементов, и,NoSuchElementException
если поток пустой.Использование:
источник
MoreCollectors
является частью еще не выпущенной (по состоянию на 2016-12 гг.) Неизданной версии 21.Операция "escape-штриховки", которая позволяет вам делать странные вещи, которые иначе не поддерживаются потоками, состоит в том, чтобы запросить
Iterator
:У Гуавы есть удобный метод, чтобы взять
Iterator
и получить единственный элемент, выбрасывая, если есть ноль или несколько элементов, которые могут заменить здесь нижние n-1 строки.источник
Обновить
Хорошее предложение в комментарии от @Holger:
Оригинальный ответ
Исключение выдается
Optional#get
, но если у вас есть более одного элемента, который не поможет. Вы можете собрать пользователей в коллекции, которая принимает только один элемент, например:который бросает
java.lang.IllegalStateException: Queue full
, но это кажется слишком хакерским.Или вы можете использовать сокращение в сочетании с дополнительным:
Сокращение по существу возвращает:
Результат затем оборачивается в необязательный.
Но простейшим решением, вероятно, было бы просто собрать коллекцию, проверить, что ее размер равен 1, и получить единственный элемент.
источник
null
), чтобы предотвратить использованиеget()
. К сожалению, выreduce
не работаете так, как вам кажется, рассмотрите элемент, вStream
котором естьnull
элементы, может быть, вы думаете, что вы его рассмотрели, но я могу быть[User#1, null, User#2, null, User#3]
, теперь он не выдаст исключение, я думаю, если я не ошибаюсь здесь.null
к функции восстановления, удаление аргумента значения идентификации будет оказывать все дело сnull
в функции устаревшее:reduce( (u,v) -> { throw new IllegalStateException("More than one ID found"); } )
делает работу , и даже лучше, это уже возвращаетOptional
, eliding необходимости для вызоваOptional.ofNullable
на результат.Альтернативой является использование сокращения: (этот пример использует строки, но может легко применяться к любому типу объекта, включая
User
)Так что для случая с
User
вами будет иметь:источник
Используя уменьшить
Это более простой и гибкий способ, который я нашел (основываясь на ответе @prunge)
Таким образом, вы получите:
Optional.empty()
если нетисточник
Я думаю, что этот способ более прост:
источник
Используя
Collector
:Использование:
Мы возвращаем
Optional
, так как обычно мы не можем предполагать, что онCollection
содержит ровно один элемент. Если вы уже знаете, что это так, позвоните:Это возлагает бремя обработки ошибки на вызывающего абонента - как и должно быть.
источник
Гуава имеет
Collector
для этого называетсяMoreCollectors.onlyElement()
.источник
Мы можем использовать RxJava (очень мощная библиотека реактивных расширений )
Одного оператора генерирует исключение , если ни один пользователь или более , то один пользователь не будет найден.
источник
Так как
Collectors.toMap(keyMapper, valueMapper)
использует одноразовое слияние для обработки нескольких записей одним и тем же ключом, это легко:Вы получите
IllegalStateException
за дубликаты ключей. Но в конце я не уверен, что код не будет еще более читабельным при использованииif
.источник
.collect(Collectors.toMap(user -> "", Function.identity())).get("")
, у вас есть более общее поведение.Я использую эти два сборщика:
источник
onlyOne()
выбрасываетIllegalStateException
для> 1 элемента и NoSuchElementException` (inOptional::get
) для 0 элементов.Supplier
из(Runtime)Exception
.Если вы не возражаете против использования сторонней библиотеки, то
SequenceM
у циклопов-потоков (иLazyFutureStream
у простых-реагирующих ) у обоих есть операторы single и singleOptional.singleOptional()
выдает исключение, если в0
или есть несколько1
элементов, вStream
противном случае возвращается единственное значение.singleOptional()
возвращает,Optional.empty()
если в. нет значений или более одного значенияStream
.Раскрытие - я автор обеих библиотек.
источник
Я пошел с прямым подходом и просто реализовал вещь:
с помощью теста JUnit:
Эта реализация не безопасна.
источник
источник
Вы пробовали это
Источник: https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html.
источник
count()
это не очень хорошо для использования, потому что это терминальная операция.