Как найти первое совпадение или последний элемент в списке с помощью потока Java?
Это означает, что если ни один элемент не соответствует условию ,, тогда возвращается последний элемент.
например:
OptionalInt i = IntStream.rangeClosed(1,5)
.filter(x-> x == 7)
.findFirst();
System.out.print(i.getAsInt());
Что делать, чтобы вернуть 5;




если вы хотите сделать это в одном конвейере, вы можете:
int startInc = 1;
int endEx = 5;
OptionalInt first =
IntStream.concat(IntStream.range(startInc, endEx)
.filter(x -> x == 7), endEx > 1 ? IntStream.of(endEx) : IntStream.empty())
.findFirst();
но вам, вероятно, лучше собрать сгенерированные числа в список, а затем действовать с ним следующим образом:
// first collect the numbers into a list
List<Integer> result = IntStream.rangeClosed(startInc,endEx)
.boxed()
.collect(toList());
// then operate on it
int value = result.stream()
.filter(x -> x == 7)
.findFirst()
.orElse(result.get(result.size() - 1));
В качестве альтернативы, если вы хотите, чтобы последний возвращал пустой Optional в случае пустого источника (если это возможный сценарий) вместо исключения, вы можете сделать:
List<Integer> result = IntStream.rangeClosed(startInc,endEx)
.boxed()
.collect(toList());
Optional<Integer> first =
Stream.concat(result.stream().filter(x -> x == 7), result.isEmpty() ?
Stream.empty() : Stream.of(result.get(result.size() - 1)))
.findFirst();
Учитывая список
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Вы могли просто сделать:
int value = list.stream().filter(x -> x == 2)
.findFirst()
.orElse(list.get(list.size() - 1));
Здесь, если фильтр оценивает значение true, элемент извлекается, иначе возвращается последний элемент в последнем.
Если список - пустой, вы можете вернуть значение по умолчанию, например -1.
int value = list.stream().filter(x -> x == 2)
.findFirst()
.orElse(list.isEmpty() ? -1 : list.get(list.size() - 1));
Вы можете использовать функцию reduce() так:
OptionalInt i = IntStream.rangeClosed(1, 5)
.reduce((first, second) -> first == 7 ? first : second);
System.out.print(i.getAsInt());
@daniu единственная проблема заключается в том, что это не короткое замыкание, поэтому, если ваше первое совпадение окажется на первом элементе, вам все равно придется пройти весь источник потока, даже если вы уже знаете результат.
@ Евгений полностью согласен. В основном я бы использовал LinkedHashSet для проверки существования элемента и в противном случае возвращал бы последний элемент.
@Eugene хороший замечание, но короткое замыкание ли reduce ()? Кроме того, на самом деле существует reduce () с элементом идентификации, но к тому времени, когда я его нашел, я уже не редактировал.
@daniu не требуется специальной обработки одиночных потоков элементов. Кроме того, для пустого потока это просто возвращает пустой Optional, который позволяет справляться с ситуацией любым удобным для вас способом, без необходимости изобретать элемент идентификации для операции.
@daniu нет такого reduce, который был бы короткозамкнутым, в нашей базе кода на работе мы генерируем исключение без трассировки стека для выхода из сокращения, но обход источника в нашем случае немного дороже, то есть причина, по которой мы это делаем
reduce предназначен не для поиска, а для ассоциативных операций, то есть для суммирования элементов. Для этого в Stream есть методы, называемые findAny и findFirst. Кроме того, если бы поток был параллельным, это не сработало бы, в то время как findFirst выполнял бы точную работу, несмотря на характеристики потока. Как заявляли другие, это тоже не вызывает короткого замыкания ...
@FedericoPeraltaSchaffner: эта функция ассоциативна, поэтому у нее не будет проблем с параллельным выполнением. Так что проблема только в том, что это неэффективно.
@Holger Я поправляюсь. Функция на самом деле ассоциативна, поэтому математически верна.
Я не уверен, почему вы действительно хотите использовать для этого потоки, простого цикла for будет достаточно:
public static <T> T getFirstMatchingOrLast(List<? extends T> source, Predicate<? super T> predicate){
// handle empty case
if (source.isEmpty()){
return null;
}
for(T t : source){
if (predicate.test(t)){
return t;
}
}
return source.get(source.size() -1);
}
Что тогда можно назвать так:
Integer match = getFirstMatchingOrLast(ints, i -> i == 7);
В основном я бы использовал один из следующих двух методов или их отклонения:
Вариант потока:
<T> T getFirstMatchOrLast(List<T> list, Predicate<T> filter, T defaultValue) {
return list.stream()
.filter(filter)
.findFirst()
.orElse(list.isEmpty() ? defaultValue : list.get(list.size() - 1));
}
непотоковый вариант:
<T> T getFirstMatchOrLast(Iterable<T> iterable, Predicate<T> filter, T defaultValue) {
T relevant = defaultValue;
for (T entry : iterable) {
relevant = entry;
if (filter.test(entry))
break;
}
return relevant;
}
Или, как также Илмари Каронен, предложенный в комментарии к Iterable<T>, вы можете даже вызвать stream::iterator, если вы действительно имеете дело с Stream вместо List. Вызов показанных методов будет выглядеть следующим образом:
getFirstMatchOrLast(Arrays.asList(1, 20, 3), i -> i == 20, 1); // returns 20
getFirstMatchOrLast(Collections.emptyList(), i -> i == 3, 20); // returns 20
getFirstMatchOrLast(Arrays.asList(1, 2, 20), i -> i == 7, 30); // returns 20
// only non-stream variant: having a Stream<Integer> stream = Stream.of(1, 2, 20)
getFirstMatchOrLast(stream::iterator, i -> i == 7, 30); // returns 20
Я бы не стал использовать здесь reduce, потому что для меня это звучит неправильно в том смысле, что он также проходит через все записи, даже если первая запись уже могла быть сопоставлена, т.е. она больше не замыкается. Более того, для меня он не такой читабельный, как filter.findFirst.orElse ... (но это, наверное, только мое мнение)
Я, вероятно, тогда даже получу что-то вроде этого:
<T> Optional<T> getFirstMatchOrLast(Iterable<T> iterable, Predicate<T> filter) {
T relevant = null;
for (T entry : iterable) {
relevant = entry;
if (filter.test(entry))
break;
}
return Optional.ofNullable(relevant);
}
// or transform the stream variant to somethinng like that... however I think that isn't as readable anymore...
так что звонки будут выглядеть так:
getFirstMatchOrLast(Arrays.asList(1, 2, 3, 5), i -> i == 7).orElseThrow(...)
getFirstMatchOrLast(Arrays.asList(1, 2, 3, 5), i -> i == 7).orElse(0);
getFirstMatchOrLast(Arrays.asList(1, 2, 3, 5), i -> i == 7).orElseGet(() -> /* complex formula */);
getFirstMatchOrLast(stream::iterator, i -> i == 5).ifPresent(...)
Мне нравится ваш "непотоковый вариант" (с необязательным или без него), но я бы предложил обобщить его, чтобы он принимал любой Iterable<T>, а не только List<T>. Таким образом, если вы действительно выполняете необходимость для обработки потока, вы можете просто передать stream::iterator этому методу.
Это блестяще. Однако вы можете захотеть использовать сборщик
reducing (), который позволяет вам указывать элемент идентификации, чтобы вы могли обрабатывать пустые потоки и потоки с одним элементом.