Найти первый элемент по предикату
Я только начал изучать лямбды в Java 8 и пытаюсь реализовать некоторые функции, с которыми я привык работать в функциональных языках.
Например, в большинстве функциональных языков есть функция find
, которая работает с последовательностями или списками и возвращает первый элемент, для которого предикат возвращает true
. Единственный способ, который я вижу, чтобы достичь этого в Java 8, выглядит так:
lst.stream()
.filter(x -> x > 5)
.findFirst()
Однако это кажется мне неэффективным, так как фильтрация будет проходить по всему списку, по крайней мере, в моем понимании (которое может быть ошибочным). Есть ли более эффективный способ сделать это?
5 ответ(ов)
Нет, метод 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
Вы видите, что на самом деле обрабатываются только два первых элемента потока.
Таким образом, вы можете использовать свой подход, он совершенно оправдан.
Вы можете использовать следующий код для фильтрации объекта из списка по заданному идентификатору. В этом примере я получаю список парковок и фильтрую его, оставляя только тот объект, у которого идентификатор совпадает с переданным. Если такой объект не найден, возвращается null
:
return dataSource.getParkingLots()
.stream()
.filter(parkingLot -> Objects.equals(parkingLot.getId(), id))
.findFirst()
.orElse(null);
Надеюсь, это поможет вам!
Уже ответил @AjaxLeung, но комментарии сложно найти.
Чтобы проверить наличие хотя бы одного элемента, который больше 5, можно использовать следующий код:
lst.stream()
.filter(x -> x > 5)
.findFirst()
.isPresent()
Этот код можно упростить до:
lst.stream()
.anyMatch(x -> x > 5)
anyMatch
проверяет, есть ли хотя бы один элемент, соответствующий условию, что делает код более лаконичным и читаемым.
В вашем коде вы сравниваете производительность двух подходов для нахождения первого элемента списка, удовлетворяющего определенному условию: использование Stream API и обычного цикла.
Исходя из вашего теста, вы заметили, что использование Stream API выполняется значительно медленнее (около 55 мс), чем традиционный цикл (около 2 мс) при 100 итерациях.
Вот некоторые причины, по которым это может происходить:
Накладные расходы: Stream API имеет накладные расходы на создание потока, фильтрацию и выполнение других операций. Для простых операций, таких как фильтрация и нахождение первого элемента, традиционный цикл может быть более эффективным.
Оптимизация компилятора: Циклы могут быть более эффективно оптимизированы компилятором. Java виртуальная машина (JVM) может выполнять дополнительные оптимизации для обычного кода, так как он более предсказуем.
Прочие факторы: Различия в использовании памяти, количества создаваемых объектов и других факторов могут сказываться на производительности.
Если вам важна производительность, то в данном случае предпочтительнее использовать обычные циклы для простых операций, как это показано в вашем тесте. Однако если вам нужен более выразительный и стилизованный код, около ситуации может быть использован 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); // Выводим результат
}
}
}
Таким образом, в зависимости от ваших требований к производительности и читаемости кода, вы можете выбрать наиболее подходящий вариант.
Ваше решение с использованием обобщенной функции для поиска элементов в списке и массиве выглядит очень аккуратно и удобно. Ваша функция 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
Как видно из примера, вы можете легко и быстро находить элементы по заданному критерию. Использование предикатов также добавляет гибкости. Если ваша задача включает в себя поиск различных типов данных с похожей логикой, то такой подход будет весьма уместным.
Если у вас остались вопросы или вам нужны дополнительные пояснения, не стесняйтесь задавать их!
Java 8: Преобразование List<V> в Map<K, V>
Java 8: Уникальные элементы по свойству (Distinct by property)
Сортировка ArrayList пользовательских объектов по свойству
Как установить Java 8 на Mac
Как преобразовать список списков в список в Java 8?