Используйте Java 8 Stream для обработки данных, таких как SQL (ниже)

Java Android

существуетпредыдущий постВ разделе мы представили, что потоки могут манипулировать коллекциями, как базами данных, но неflatMapиcollectработать. Эти две операции очень полезны для реализации сложных запросов. Например, вы можете привестиflatMapиcollectПодсчитайте количество символов в слове в потоке, как в приведенном ниже коде.

import static java.util.function.Function.identity;
import static java.util.stream.Collectors.*;

Stream<String> words = Stream.of("Java", "Magazine", "is", "the", "best");

Map<String, Long> letterToCount =words.map(w -> w.split(""))
                .flatMap(Arrays::stream)
                .collect(groupingBy(identity(), counting()));

Результат выполнения приведенного выше кода:

 [a:4, b:1, e:3, g:1, h:1, i:2, ..]

В этой статье будут представлены более подробные сведения о двух операциях: flatMap и collect.

операция flatMap

Предположим, вы ищете слово в статье, что бы вы сделали?

мы можем использоватьFiles.lines()метод, потому что он может возвращать поток информации построчно. мы можем использоватьmap()Разделите каждую строку статьи на несколько слов и, наконец, используйте `distinct()` для удаления дубликатов. Превращаем идеи в код:

Files.lines(Paths.get("stuff.txt"))
              .map(line -> line.split("\\s+")) 
              .distinct() // Stream<String[]>
              .forEach(System.out::println);

К сожалению, это неправильно. Если вы запустите и получите этот результат:

[Ljava.lang.String;@7cca494b
[Ljava.lang.String;@7ba4f24f
…

Что, черт возьми, случилось? Проблема в том, что используемое лямбда-выражение преобразует каждую строку файла в массив строк (String[]). Это приводит к тому, что map возвращает результат типа Stream, а нам действительно нужен результат типа Stream.

Нам нужна строка слов, а не строка массивов. Для массивов вы можете использоватьArrays.stream()Превратите массив в поток. См. реализацию ниже:

String[] arrayOfWords = {"Java", "Magazine"};
Stream<String> streamOfwords = Arrays.stream(arrayOfWords);

Если мы используем следующий метод, он не работает, потому что использованиеmap(Arrays::stream)После возврата на самом деле тип Stream .

Files.lines(Paths.get("stuff.txt"))
            .map(line -> line.split("\\s+")) // Stream<String[]>
            .map(Arrays::stream) // Stream<Stream<String>>
            .distinct() // Stream<Stream<String>>
            .forEach(System.out::println);

FlatMap мы можем использовать для решения этой проблемы, как показано ниже. Метод FlatMap, использующий роль для возврата потока содержимого, не является потоком.

Files.lines(Paths.get("stuff.txt"))
            .map(line -> line.split("\\s+")) // Stream<String[]>
            .flatMap(Arrays::stream) // Stream<String>
            .distinct() // Stream<String>
            .forEach(System.out::println);

операция сбора

Давайте подробно рассмотрим операцию сбора. В приведенной выше статье мы видели операцию возврата потока (указывающую, что операция является промежуточной операцией) и операцию возврата значения, значения логического типа, значения типа int и значения необязательного типа (указывающего, что операция является конечной операцией). ) .

Преобразуйте элементы потока в коллекцию.

Используя toSet(), вы можете преобразовать поток в набор без дубликатов. В следующем коде показано, как создать набор городов с высоким потреблением (одна транзакция > 1000$).

Set<String> cities = transactions.stream()
                   .filter(t -> t.getValue() > 1000)
                   .map(Transaction::getCity)
                   .collect(toSet());

Обратите внимание, что таким образом вы не можете гарантировать, какой тип Set будет возвращен, вы можете использовать toCollection() для улучшения управляемости. Например, вы можете передать конструктор HashSet в качестве параметра, как показано в следующем коде.

Set<String> cities = transactions.stream()
                    .filter(t -> t.getValue() > 1000)
                    .map(Transaction::getCity)
                    .collect(toCollection(HashSet::new));

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

  • Сгруппируйте по типу валюты и рассчитайте общую сумму транзакции для каждого типа выборки (вернет Map)

  • Разделите все транзакции на две группы: крупные и некрупные (вернет Map)

  • Создайте многоуровневую группировку, например, сначала группируйте по городу, а затем группируйте по тому, является ли это крупной транзакцией (вернет Map>)

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

Summarizing

Существует множество предопределенных агрегаторов, и их очень удобно использовать, например, counting() для подсчета числа:

long howManyTransactions = transactions.stream().collect(counting());

Вы можете выполнять операции суммирования Double(), summingInt() и summingLong() для элементов со свойствами Double, Int или Long, например:

int totalValue = transactions.stream().collect(summingInt(Transaction::getValue));

Точно так же вы можете использовать averagingDouble(), averagingInt() и averagingLong() для вычисления среднего, например:

double average = transactions.stream().collect(averagingInt(Transaction::getValue));

Вы также можете вычислить максимальное и минимальное значения элементов, используя maxBy() и minBy(), но вам нужно определить компаратор для сравнения, поэтому maxBy и minBy требуют объект Comparator в качестве параметра:

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

Optional<Transaction> highestTransaction = transactions.stream()
                .collect(maxBy(comparing(Transaction::getValue)));

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

int totalValue = transactions.stream().collect(reducing(0, Transaction::getValue, Integer::sum));

Сокращение() имеет три параметра:

  • Начальное значение (также возвращается, если поток пуст): здесь 0
  • метод, который будет применяться к каждому элементу
  • Объедините два извлеченных значения, то есть добавьте два значения

Grouping

Распространенной операцией с базой данных является группировка данных в соответствии с атрибутом. Например, группировка транзакций по валюте слишком сложна для многократного использования:

Map<Currency, List<Transaction>> transactionsByCurrencies = new HashMap<>();

for(Transaction transaction : transactions) { 
        Currency currency = transaction.getCurrency();
        List<Transaction> transactionsForCurrency = transactionsByCurrencies.get(currency);
        if (transactionsForCurrency == null) {
            transactionsForCurrency = new ArrayList<>();
            transactionsByCurrencies.put(currency, transactionsForCurrency);
        }
        transactionsForCurrency.add(transaction);
}

В Java 8 естьgroupingBy()агрегатор, мы можем запросить его следующим образом:

Map<Currency, List<Transaction>> transactionsByCurrencies =
    transactions.stream().collect(groupingBy(Transaction::getCurrency));

Метод groupingBy() имеет функцию для извлечения ключа классификации в качестве параметра, мы можем назвать ее функцией классификации. В этом примере мы используем Transaction::getCurrency для группировки по валюте.

Partitioning

Существует также функция partitioningBy(), которую можно рассматривать как частный случай groupingBy(). Он принимает предикат (функция, возвращающая логическое значение) в качестве параметра и классифицирует элементы в потоке в зависимости от того, удовлетворяют ли они этому предикату. Разделение может превратить поток в Map. Используйте код следующим образом:

Map<Boolean, List<Transaction>> partitionedTransactions =transactions.stream().collect(partitioningBy( t -> t.getValue() > 1000));

Если вы хотите суммировать суммы в разных валютах, вы можете использовать SUM и GROUP BY вместе в SQL. Можем ли мы сделать то же самое с Stream API? Конечно, можно, используйте это так:

Map<String, Integer> cityToSum = transactions.stream()
                                .collect(groupingBy(Transaction::getCity, summingInt(Transaction::getValue)));

Ранее использовавшееся groupingBy (Transaction::getCity) на самом деле является сокращением для groupingBy (Transaction::getCity, toList()).

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

Map<String, Optional<Transaction>> cityToHighestTransaction = 
           transactions.stream().collect(groupingBy(
             Transaction::getCity, maxBy(comparing(Transaction::getValue))));

Глядя на более сложный пример, в предыдущем примере мы передали другой агрегатор в качестве параметра groupingBy для дальнейшей группировки элементов. Поскольку groupingBy сам по себе является агрегатором, мы можем создать многоуровневую группировку, передав другие агрегаторы groupingBy.Переданный в groupingBy определяет вторичный стандарт, который может перегруппировать элементы в потоке.

В следующем коде мы сначала группируем города, а затем группируем их в соответствии со средним значением транзакций каждого города в разных валютах.

Map<String, Map<Currency, Double>> cityByCurrencyToAverage = 
        transactions.stream().collect(groupingBy(Transaction::getCity,groupingBy(Transaction::getCurrency, averagingInt(Transaction::getValue))));

пользовательский агрегатор

Агрегаторы, которые мы видели, реализованыjava.util.stream .Collectorинтерфейс.这就意味着你可以自定义集合器。

Суммировать

В этом посте мы рассмотрели расширенные операции двух Stream API: flatMap и corelct. С помощью этих двух операций вы можете создавать более сложные запросы обработки данных.

Мы также реализуем операции суммирования, группировки и разделения с помощью метода collect. Эти операции также можно комбинировать для создания более сложных запросов.

Наконец

Спасибо, что прочитали. Если вам интересно, вы можете подписаться на общедоступную учетную запись WeChat, чтобы получать последние push-статьи.

欢迎关注微信公众号
Добро пожаловать в публичный аккаунт WeChat