7

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

1

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

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

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

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

5 ответ(ов)

9

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

List<Integer> list = Arrays.asList(1, 10, 3, 7, 5);
int a = list.stream()
            .peek(num -> System.out.println("будет отфильтровано " + num))
            .filter(x -> x > 5)
            .findFirst()
            .get();
System.out.println(a);

Результат будет следующим:

будет отфильтровано 1
будет отфильтровано 10
10

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

Таким образом, вы можете использовать свой подход, он совершенно оправдан.

0

Вы можете использовать следующий код для фильтрации объекта из списка по заданному идентификатору. В этом примере я получаю список парковок и фильтрую его, оставляя только тот объект, у которого идентификатор совпадает с переданным. Если такой объект не найден, возвращается null:

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

Надеюсь, это поможет вам!

0

Уже ответил @AjaxLeung, но комментарии сложно найти.

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

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

Этот код можно упростить до:

lst.stream()
    .anyMatch(x -> x > 5)

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

0

В вашем коде вы сравниваете производительность двух подходов для нахождения первого элемента списка, удовлетворяющего определенному условию: использование Stream API и обычного цикла.

Исходя из вашего теста, вы заметили, что использование Stream API выполняется значительно медленнее (около 55 мс), чем традиционный цикл (около 2 мс) при 100 итерациях.

Вот некоторые причины, по которым это может происходить:

  1. Накладные расходы: Stream API имеет накладные расходы на создание потока, фильтрацию и выполнение других операций. Для простых операций, таких как фильтрация и нахождение первого элемента, традиционный цикл может быть более эффективным.

  2. Оптимизация компилятора: Циклы могут быть более эффективно оптимизированы компилятором. Java виртуальная машина (JVM) может выполнять дополнительные оптимизации для обычного кода, так как он более предсказуем.

  3. Прочие факторы: Различия в использовании памяти, количества создаваемых объектов и других факторов могут сказываться на производительности.

Если вам важна производительность, то в данном случае предпочтительнее использовать обычные циклы для простых операций, как это показано в вашем тесте. Однако если вам нужен более выразительный и стилизованный код, около ситуации может быть использован Stream API, когда производительность не является критическим фактором.

Вот ваш код с некоторыми комментариями для ясности:

import org.junit.Test;

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

public class StreamPerfTest {

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

    // Время выполнения около 55 мс
    @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 мс
    @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); // Выводим результат
        }
    }
}

Таким образом, в зависимости от ваших требований к производительности и читаемости кода, вы можете выбрать наиболее подходящий вариант.

0

Ваше решение с использованием обобщенной функции для поиска элементов в списке и массиве выглядит очень аккуратно и удобно. Ваша функция find принимает список или массив элементов и предикат, который определяет условие поиска.

Вот ваш код наилегчайшим образом:

static public <T> T find(List<T> elements, Predicate<T> p) {
    for (T item : elements) if (p.test(item)) return item;
    return null;
}

static public <T> T find(T[] elements, Predicate<T> p) {
    for (T item : elements) if (p.test(item)) return item;
    return null;
}

Таким образом, вы делаете код более читаемым и поддерживаемым, избегая дублирования логики поиска для различных структур данных. Хороший подход к реюзу кода!

Пример использования:

List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5);
Integer[] intArr = new Integer[]{1, 2, 3, 4, 5};

System.out.println(find(intList, i -> i % 2 == 0)); // 2
System.out.println(find(intArr, i -> i % 2 != 0)); // 1
System.out.println(find(intList, i -> i > 5)); // null

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

Если у вас остались вопросы или вам нужны дополнительные пояснения, не стесняйтесь задавать их!

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь