Оптимизация кода с помощью Stream API

Java

Новые функции Java 8 в основном связаны с лямбда-выражениями и потоками.Когда потоки и лямбда-выражения используются вместе, код можно сделать кратким и легко читаемым благодаря характеристикам объявлений потоков для обработки наборов данных.

Увеличьте масштаб, как потоки упрощают ваш код

Если есть требование, необходимо выполнить процесс для блюд, запрошенных из базы данных:

  • Отфильтруйте блюда с менее чем 400 калориями
  • Сортировать отфильтрованные блюда
  • Получить названия отсортированных блюд
Блюдо: Dish.java
public class Dish {
    private String name;
    private boolean vegetarian;
    private int calories;
    private Type type;

    // getter and setter
}
Реализация до Java 8
private List<String> beforeJava7(List<Dish> dishList) {
        List<Dish> lowCaloricDishes = new ArrayList<>();
        
        //1.筛选出卡路里小于400的菜肴
        for (Dish dish : dishList) {
            if (dish.getCalories() < 400) {
                lowCaloricDishes.add(dish);
            }
        }
        
        //2.对筛选出的菜肴进行排序
        Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
            @Override
            public int compare(Dish o1, Dish o2) {
                return Integer.compare(o1.getCalories(), o2.getCalories());
            }
        });
        
        //3.获取排序后菜肴的名字
        List<String> lowCaloricDishesName = new ArrayList<>();
        for (Dish d : lowCaloricDishes) {
            lowCaloricDishesName.add(d.getName());
        }
        
        return lowCaloricDishesName;
    }
Реализация после Java 8
 private List<String> afterJava8(List<Dish> dishList) {
        return dishList.stream()
                .filter(d -> d.getCalories() < 400)  //筛选出卡路里小于400的菜肴
                .sorted(comparing(Dish::getCalories))  //根据卡路里进行排序
                .map(Dish::getName)  //提取菜肴名称
                .collect(Collectors.toList()); //转换为List
    }

Не халтурьте, просто сделайте это за один раз, получается, что нужно писать24Функция, реализованная кодом, теперь должна только5ок и готово

Рад закончить написание требований. На данный момент есть новые требования. Новые требования заключаются в следующем:

  • Классифицируйте блюда, запрошенные из базы данных, в соответствии с типом блюд и возвращайтеMap<Type, List<Dish>>результат

Если поставить перед jdk8, то кожа головы точно онемеет.

Реализация до Java 8
private static Map<Type, List<Dish>> beforeJdk8(List<Dish> dishList) {
    Map<Type, List<Dish>> result = new HashMap<>();

    for (Dish dish : dishList) {
        //不存在则初始化
        if (result.get(dish.getType())==null) {
            List<Dish> dishes = new ArrayList<>();
            dishes.add(dish);
            result.put(dish.getType(), dishes);
        } else {
            //存在则追加
            result.get(dish.getType()).add(dish);
        }
    }

    return result;
}

К счастью, в jdk8 есть Stream, поэтому вам больше не нужно беспокоиться о сложных требованиях к обработке коллекций.

Реализация после Java 8
private static Map<Type, List<Dish>> afterJdk8(List<Dish> dishList) {
    return dishList.stream().collect(groupingBy(Dish::getType));
}

Еще одна строка кода решает спрос, не могу не кричатьStream APIпартия крупного рогатого скота Посмотрите мощные функции потока, а затем мы подробно расскажем о потоке.

что такое стрим

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

Как создать поток

Существует пять основных способов генерации потоков

  • Генерируется коллекциями, наиболее часто используемой в приложениях.
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = integerList.stream();

по коллекцииstreamметод создания потока

  • Генерируется из массива
    int[] intArr = new int[]{1, 2, 3, 4, 5};
    IntStream stream = Arrays.stream(intArr);
    

пройти черезArrays.streamметод создает поток, а поток, создаваемый методом, является потоком значений [т.е.IntStream] вместоStream<Integer>. Добавьте, что использование числовых потоков позволяет избежать распаковки во время вычислений и повысить производительность.Stream APIпри условииmapToInt,mapToDouble,mapToLongТри способа потоковой передачи объектов [т.е.Stream<T>] в соответствующий числовой поток, обеспечивая при этомboxedметод преобразует поток значений в поток объектов

  • Создано по значению
    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
    

пройти черезStreamизofспособ создания потока черезStreamизemptyметод может генерировать пустой поток

  • Генерируется из файла
    Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())
    

пройти черезFiles.lineМетод получения потока, каждый поток получается и заданная строка в файле

  • Генерируется функцией при условииiterateиgenerateДва статических метода генерируют потоки из функций
    • iterator
    Stream<Integer> stream = Stream.iterate(0, n -> n + 2).limit(5);
    
    iterateМетод принимает два параметра, первый — это значение инициализации, а второй — выполняемая функция, потому чтоiteratorРезультирующий поток является бесконечным потоком, черезlimitМетод усекает поток и генерирует только 5 четных чисел.
    • generator
    Stream<Double> stream = Stream.generate(Math::random).limit(5);
    
    generateМетод принимает один параметр, а тип параметра метода —Supplier<T>, который предоставляет значения для потока.generateРезультирующий поток также является бесконечным потоком, поэтомуlimitусеченный поток

тип операции потока

Существует два основных типа потоковых операций.

  • Промежуточная операция За потоком может следовать ноль или более промежуточных операций. Его цель в основном состоит в том, чтобы открыть поток, выполнить некоторую степень сопоставления/фильтрации данных, а затем вернуть новый поток для использования следующей операцией. Этот тип операции является ленивым. Только вызов таких методов не запускает обход потока в действительности. Настоящему обходу нужно дождаться терминальной операции. Ниже описаны общие промежуточные операции.filter,mapЖдать
  • терминальная операция Поток имеет одну и только одну терминальную операцию.При выполнении этой операции поток закрывается и больше не может управляться.Поэтому поток можно пройти только один раз.Если вы хотите пройти, вам нужно сгенерировать поток через исходные данные. Выполнение терминальной операции действительно запустит обход потока. Как будет представлено нижеcount,collectЖдать

Использование потока

Использование потоков будет разделено на терминальные операции и промежуточные операции.

Промежуточная операция

фильтрфильтр
 List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);
 Stream<Integer> stream = integerList.stream().filter(i -> i > 3);

используяfilterметод условной фильтрации,filterПараметр метода является условием

отличительный удаляет повторяющиеся элементы
List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);
Stream<Integer> stream = integerList.stream().distinct();

пройти черезdistinctспособ быстрого удаления повторяющихся элементов

limit возвращает указанное количество потоков
 List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);
 Stream<Integer> stream = integerList.stream().limit(3);

пройти черезlimitМетод указывает количество возвращенных потоков,limitЗначение параметра должно быть>=0, иначе будет выброшено исключение

skip пропускает элементы в потоке
 List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);
 Stream<Integer> stream = integerList.stream().skip(2);

пройти черезskipМетод пропускает элементы в потоке, приведенный выше пример пропускает первые два элемента, поэтому результат печати2,3,4,5,skipЗначение параметра должно быть>=0, иначе будет выброшено исключение

карта потока карта

Так называемое отображение потока заключается в сопоставлении принятого элемента с другим элементом.

List<String> stringList = Arrays.asList("Java 8", "Lambdas",  "In", "Action");
Stream<Integer> stream = stringList.stream().map(String::length);

пройти черезmapМетод может завершить сопоставление, пример завершенString -> Integerотображение, прежде чем приведенный выше пример пройдетmapметод завершенDish->Stringотображение

Преобразование потока flatMap

Преобразование каждого значения в одном потоке в другой

List<String> wordList = Arrays.asList("Hello", "World");
List<String> strList = wordList.stream()
        .map(w -> w.split(" "))
        .flatMap(Arrays::stream)
        .distinct()
        .collect(Collectors.toList());

map(w -> w.split(" "))Возвращаемое значениеStream<String[]>, мы хотим получитьStream<String>, в состоянии пройтиflatMapметод завершенStream<String[]> ->Stream<String>преобразование

совпадение элементов

Предусмотрены три метода сопоставления

  • allMatch соответствует всем
    List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
    if (integerList.stream().allMatch(i -> i > 3)) {
        System.out.println("值都大于3");
    }
    

пройти черезallMatchреализация метода

  • anyMatch соответствует одному из
    List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
    if (integerList.stream().anyMatch(i -> i > 3)) {
        System.out.println("存在大于3的值");
    }
    
    Эквивалентно
    for (Integer i : integerList) {
        if (i > 3) {
            System.out.println("存在大于3的值");
            break;
        }
    }
    

Если есть значение больше 3, выведите,java8прошедшийanyMatchметод достижения этого

  • noneMatch не соответствует всем
    List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
    if (integerList.stream().noneMatch(i -> i > 3)) {
        System.out.println("值都小于3");
    }
    
    пройти черезnoneMatchреализация метода

терминальная операция

подсчитать количество элементов в потоке
  • по количеству
    List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
    Long result = integerList.stream().count();
    

используяcountМетод подсчитывает количество элементов в потоке

  • путем подсчета
    List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
    Long result = integerList.stream().collect(counting());
    

Последний способ подсчета количества элементов — этоcollectОсобенно полезно при использовании в сочетании

найти

Предусмотрено два метода поиска

  • findFirst найти первый

    List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
    Optional<Integer> result = integerList.stream().filter(i -> i > 3).findFirst();
    

    пройти черезfindFirstметод находит первый элемент больше трех и печатает

  • findAny находит случайный

    List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
    Optional<Integer> result = integerList.stream().filter(i -> i > 3).findAny();
    

    пройти черезfindAnyМетод находит один из элементов больше трех и выводит его.Из-за внутренней оптимизации он завершается, когда будет найден первый элемент больше трех.Результат метода такой же, какfindFirstРезультаты метода те же. поставкаfindAnyМетод состоит в том, чтобы лучше использовать параллельные потоки,findFirstПараллельные методы более ограничены [параллельные потоки в этой статье не рассматриваются]

сокращение объединяет элементы в потоке

Предположим, мы суммируем значения в наборе

  • До jdk8

    int sum = 0;
    for (int i : integerList) {
    sum += i;
    }
    
  • После того, как jdk8 обработан с помощью сокращения

    int sum = integerList.stream().reduce(0, (a, b) -> (a + b));
    

    Это можно сделать одной строкой или сократить, используя ссылку на метод:

    int sum = integerList.stream().reduce(0, Integer::sum);
    

    reduceпринимает два параметра, начальное значение здесь0,ОдинBinaryOperator<T> accumulatorобъединить два элемента для получения нового значения, Кроме тогоreduceМетод также имеет перегруженный метод без значения инициализации.

Получить минимальное и максимальное значения в потоке
  • Получить мин и макс по мин/макс
    Optional<Integer> min = menu.stream().map(Dish::getCalories).min(Integer::compareTo);
    Optional<Integer> max = menu.stream().map(Dish::getCalories).max(Integer::compareTo);
    
    Это также может быть записано как:
    OptionalInt min = menu.stream().mapToInt(Dish::getCalories).min();
    OptionalInt max = menu.stream().mapToInt(Dish::getCalories).max();
    
    minполучить минимальное значение в потоке,maxПолучить максимальное значение в потоке, параметр методаComparator<? super T> comparator
  • Получить минимум и максимум по minBy/maxBy
    Optional<Integer> min = menu.stream().map(Dish::getCalories).collect(minBy(Integer::compareTo));
    Optional<Integer> max = menu.stream().map(Dish::getCalories).collect(maxBy(Integer::compareTo));
    
    minByполучить минимальное значение в потоке,maxByПолучить максимальное значение в потоке, параметр методаComparator<? super T> comparator
  • Получите минимум и максимум, уменьшив
    Optional<Integer> min = menu.stream().map(Dish::getCalories).reduce(Integer::min);
    Optional<Integer> max = menu.stream().map(Dish::getCalories).reduce(Integer::max);
    
сумма
  • по summingInt
    int sum = menu.stream().collect(summingInt(Dish::getCalories));
    
    Если тип данныхdouble,long, затем черезsummingDouble,summingLongметод суммирования
  • путем уменьшения
    int sum = menu.stream().map(Dish::getCalories).reduce(0, Integer::sum);
    
  • по сумме
    int sum = menu.stream().mapToInt(Dish::getCalories).sum();
    

В приведенном выше суммировании, максимальном значении и минимальном значении существуют разные методы для выполнения одной и той же операции. можешь выбратьcollect,reduce,min/max/sumметод, рекомендуется использоватьmin,max,sumметод. потому что он самый лаконичный и легко читаемый, при этом проходяmapToIntПреобразует поток объектов в поток значений, избегая операций упаковки и распаковки.

Среднее по усреднениюInt
double average = menu.stream().collect(averagingInt(Dish::getCalories));

Если тип данныхdouble,long, затем черезaveragingDouble,averagingLongметод усреднения

Одновременная сумма, среднее, максимальное, минимальное значение по summarizingInt
IntSummaryStatistics intSummaryStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));
double average = intSummaryStatistics.getAverage();  //获取平均值
int min = intSummaryStatistics.getMin();  //获取最小值
int max = intSummaryStatistics.getMax();  //获取最大值
long sum = intSummaryStatistics.getSum();  //获取总和

Если тип данныхdouble,long, затем черезsummarizingDouble,summarizingLongметод

Обход элемента с помощью foreach
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
integerList.stream().forEach(System.out::println);

И реализовать обход перед jdk8:

for (int i : integerList) {
    System.out.println(i);
}

Элементы обхода удобнее проходить после jdk8, оригинал for-each можно реализовать напрямую через метод foreach.

возврат коллекции
List<String> strings = menu.stream().map(Dish::getName).collect(toList());
Set<String> sets = menu.stream().map(Dish::getName).collect(toSet());

Всего несколько примеров, есть много других способов До jdk8

 List<String> stringList = new ArrayList<>();
    Set<String> stringSet = new HashSet<>();
    for (Dish dish : menu) {
        stringList.add(dish.getName());
        stringSet.add(dish.getName());
}

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

Объединяйте элементы в потоке путем объединения
String result = menu.stream().map(Dish::getName).collect(Collectors.joining(", "));

По умолчанию, если не пройденоmapметод сопоставления обработки сплайсингаtoStringСтрока, возвращаемая методом.Параметром метода соединения является разделитель элемента.Если строка не указана, сгенерированная строка будет строкой, которая плохо читается.

Расширенная группировка по groupingBy
Map<Type, List<Dish>> result = dishList.stream().collect(groupingBy(Dish::getType));

существуетcollectпрошел в методеgroupingByсгруппированы, гдеgroupingByПараметр метода является функцией классификации. Вы также можете использовать вложенныеgroupingByвыполнять многоуровневую классификацию

Map<Type, List<Dish>> result = menu.stream().collect(groupingBy(Dish::getType,
        groupingBy(dish -> {
            if (dish.getCalories() <= 400) return CaloricLevel.DIET;
                else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
                else return CaloricLevel.FAT;
        })));
Расширенное разбиение с помощью partitioningBy

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

Map<Boolean, List<Dish>> result = menu.stream().collect(partitioningBy(Dish :: isVegetarian))

Эквивалентно

Map<Boolean, List<Dish>> result = menu.stream().collect(groupingBy(Dish :: isVegetarian))

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

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Map<Boolean, List<Integer>> result = integerList.stream().collect(partitioningBy(i -> i < 3));

Ключ возвращаемого значения по-прежнему логического типа, но его классификация основана на диапазоне, и партиция больше подходит для обработки классификации по диапазону.

Суммировать

используяStream APIЭто может упростить код и улучшить читаемость кода одновременно, и быстро использовать его в проекте. Я не узнаю правдуStream APIРаньше кто мне много писал в заявкеLambda,Stream API, хотел дать ему пинок, когда он взлетел. Думаю, теперь я могу в него влюбиться [хи-хи]. При одновременном использовании будьте осторожны, чтобы не смешать декларативное и императивное программирование.segmentКисть к:

imangoБрат прав.Не используйте синтаксис декларативного программирования для императивного программирования