Подтвердить что ты не робот

Найти первый элемент по предикату

Я только начал играть с Java 8 lambdas, и я пытаюсь реализовать некоторые вещи, к которым я привык, в функциональных языках.

Например, большинство функциональных языков имеют некоторую функцию поиска, которая работает с последовательностями или списками, которые возвращают первый элемент, для которого предикат true. Единственный способ добиться этого в Java 8 - это:

lst.stream()
    .filter(x -> x > 5)
    .findFirst()

Однако это кажется мне неэффективным, поскольку фильтр сканирует весь список, по крайней мере, до моего понимания (что может быть неправильно). Есть ли способ лучше?

4b9b3361

Ответ 1

Нет, фильтр не сканирует весь поток. Это промежуточная операция, которая возвращает ленивый поток (фактически все промежуточные операции возвращают ленивый поток). Чтобы убедить вас, вы можете просто выполнить следующий тест:

List<Integer> list = Arrays.asList(1, 10, 3, 7, 5);
int a = list.stream()
            .peek(num -> System.out.println("will filter " + num))
            .filter(x -> x > 5)
            .findFirst()
            .get();
System.out.println(a);

Какие выходы:

will filter 1
will filter 10
10

Вы видите, что на самом деле обрабатываются только два первых элемента потока.

Итак, вы можете пойти с вашим подходом, который отлично подходит.

Ответ 2

Однако это кажется мне неэффективным, так как фильтр сканирует весь список

Нет, это не будет - он "сломается", как только будет найден первый элемент, удовлетворяющий предикату. Вы можете больше узнать о лени в поточном пакете javadoc, в частности (внимание мое):

Многие потоковые операции, такие как фильтрация, сопоставление или удаление дубликатов, могут выполняться лениво, предоставляя возможности для оптимизации. Например, "найти первую строку с тремя последовательными гласными" не нужно проверять все входные строки. Потоковые операции делятся на промежуточные (потокообразующие) операции и операции с терминалом (производящие стоимость или побочные эффекты). Промежуточные операции всегда ленивы.

Ответ 3

return dataSource.getParkingLots().stream().filter(parkingLot -> Objects.equals(parkingLot.getId(), id)).findFirst().orElse(null);

Мне пришлось отфильтровать только один объект из списка объектов. Поэтому я использовал это, надеюсь, что это поможет.

Ответ 4

Помимо ответа Alexis C, если вы работаете с списком массивов, в котором вы не уверены, существует ли тот элемент, который вы ищете, используйте это.

Integer a = list.stream()
                .peek(num -> System.out.println("will filter " + num))
                .filter(x -> x > 5)
                .findFirst()
                .orElse(null);

Тогда вы можете просто проверить, является ли значение null.

Ответ 5

Если вы ищете логическое возвращаемое значение, мы можем сделать это лучше, добавив нулевую проверку:

return dataSource.getParkingLots().stream().filter(parkingLot -> Objects.equals(parkingLot.getId(), id)).findFirst().orElse(null) != null;

Ответ 6

Уверенность определяют в обустройстве воспринимаемой ситуации или в целом противоположном. Фавор встретил сам, хотел поселиться, поставить чердак двадцать. В удивленных квартирах разрешение так оно и есть. Неутешительный по сравнению с разумными компаньонами. В противном случае нет допущения к подозрению в мебели.

Ответ 7


import org.junit.Test;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

// Stream is ~30 times slower for same operation...
public class StreamPerfTest {

    int iterations = 100;
    List<Integer> list = Arrays.asList(1, 10, 3, 7, 5);


    // 55 ms
    @Test
    public void stream() {

        for (int i = 0; i < iterations; i++) {
            Optional<Integer> result = list.stream()
                    .filter(x -> x > 5)
                    .findFirst();

            System.out.println(result.orElse(null));
        }
    }

    // 2 ms
    @Test
    public void loop() {

        for (int i = 0; i < iterations; i++) {
            Integer result = null;
            for (Integer walk : list) {
                if (walk > 5) {
                    result = walk;
                    break;
                }
            }
            System.out.println(result);
        }
    }
}

Ответ 8

Если ваш список действительно огромный (тысячи элементов), использование потоков здесь просто дорого и даже делает код более сложным для понимания.

Примечание. java НЕ является функциональным языком (и jvm специально не подходит для эффективного использования функциональных языков).

Гораздо проще и эффективнее (на всех Iterable):

for (MyType walk : lst)
    if (walk > 5) { do_whatever; break; }

Или если вы хотите пропустить итератор:

for (int x=0; x<list.size(); x++)
    if (list.get(x) > 5 { do_whatever; break; }

На самом деле, я действительно удивляюсь, почему некоторые люди предлагают этот сложный и дорогой механизм потоков, даже для простых вещей, таких как получение первого элемента массива. (да: массивы по-прежнему поддерживаются в Java8).