Руководство по Java 8 Collectors: summingDouble(), summinglong() и summingint()

Вступление

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

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

Чтобы «закрепить» изменения, вы собираете элементы потока обратно в Collection.

Многие простые математические операции и концепции находят свое применение в программировании, и чаще всего они так же просты в программировании, как и в самой математике.

В этом руководстве мы рассмотрим, как суммировать все элементы в коллекции на Java, используя summingDouble(), summinglong() и summingint().

Коллекции и Stream.collect()

Коллекции – это реализации интерфейса Collector, который реализует различные полезные операции сокращения (накопление элементов в коллекции, суммирование элементов на основе определенного параметра и т.д.).

Все предопределенные реализации можно найти в классе Collectors.

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

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

import static java.util.stream.Collectors.*;

Stream.collect() выполняет изменяемую операцию сокращения элементов потока.

Определяем коллекторы summing_()

В самом классе коллекций можно найти множество уникальных методов, которые удовлетворяют различные потребности пользователя. Одна из таких групп состоит из методов суммирования: summingint(), summingDouble() и summinglong().

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

public static <T> Collector<T,?,Integer> summingInt(ToIntFunction<? super T> mapper)

public static <T> Collector<T,?,Double> summingDouble(ToDoubleFunction<? super T> mapper)

public static <T> Collector<T,?,Long> summingLong(ToLongFunction<? super T> mapper)

Ранее мы говорили, что эти методы могут использоваться только для числовых вводов. Предопределённые функции ToIntFunction, ToDoubleFunction и ToLongFunction из java.util.function позволяют нам выполнять именно такие преобразования – от типов объектов к их примитивным типам int, double, long.

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

Этот класс назовём Product:

public class Product {
    private String name;
    private Integer quantity;
    private Double price;
    private Long productNumber;


}

В классе есть четыре поля разных типов:

  • String name: в итоге мы не будем использовать это поле, так как оно имеет нечисловое значение, но для того, чтобы иметь значимые примеры, нам нужно назвать наши продукты.
  • Integer quantity: количество товаров в нашем инвентаре.
  • Double price: цена продукта.
  • Long productNumber:шестизначный код для каждого из наших продуктов.

Наряду с этими четырьмя полями у нас также есть простой конструктор и средства получения для всего, кроме названия самого продукта. Давайте также создадим экземпляр нашего списка продуктов в основной программе:

List<Product> products = Arrays.asList(
        new Product("Milk", 37, 3.60, 12345600L),
        new Product("Carton of Eggs", 50, 1.20, 12378300L),
        new Product("Olive oil", 28, 37.0, 13412300L),
        new Product("Peanut butter", 33, 4.19, 15121200L),
        new Product("Bag of rice", 26, 1.70, 21401265L)
);

Collectors.summingInt()

Метод summingInt() возвращает коллекцию, которая выдаёт сумму целочисленной функции, применённой к входным элементам. Другими словами – он суммирует целые числа в коллекции и возвращает результат. В случае отсутствия входных элементов возвращаемое значение равно 0.

Начнём с простого примера со списком целых чисел:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Integer sum = numbers.stream().collect(Collectors.summingInt(Integer::intValue));
System.out.println("Sum: " + sum);

Мы применяем метод .stream(), чтобы создать поток целочисленных экземпляров, после чего используем ранее рассмотренный метод .collect() для сбора элементов с помощью summingInt(). Сам метод, опять же, принимает ToIntFunction, которsq можно использовать для уменьшения экземпляров до целого числа, которое можно суммировать.

Поскольку мы уже используем целые числа, мы можем просто передать ссылку на метод, обозначающую их значение, так как сокращать не надо:

Sum: 15

Чаще всего вы будете работать со списками пользовательских объектов и будете суммировать некоторые из их полей. Например, мы можем суммировать количество каждого продукта из productList, обозначая общий объем имеющихся у нас запасов.

В таком случае мы можем использовать ссылку на метод, например, Product::getquantity в качестве нашей функции ToIntFunction, чтобы свести каждый объект к одному целому числу, а затем суммировать эти целые числа:

Integer sumOfQuantities = products.stream().collect(Collectors.summingInt(Product::getQuantity));
System.out.println("Total number of products: " + sumOfQuantities);

Результат:

Total number of products: 174

Collectors.summingDouble()

Во многом таким же образом summingint() возвращает результат суммированных целых значений – summingDouble() возвращает результат суммированных двойных значений.

Однако этот метод отличается от summingInt() по одному признаку. Возвращаемая сумма может варьироваться в зависимости от порядка записи значений из-за накопленных ошибок округления. Значения, отсортированные в порядке возрастания, как правило, дают более точные результаты.

Начнём со списка двойников:

List<Double> numbers = Arrays.asList(3.0, 5.5, 11.3, 40.3, 21.1);
Double sum = numbers.stream().collect(Collectors.summingDouble(Double::doubleValue));
System.out.println(sum);

После округления мы получим:

81.2

Опять-таки, мы обычно работаем с пользовательскими объектами, а не с типами оболочек/примитивов. Цены на нашу продукцию выражены в двойном размере, поэтому мы могли бы суммировать цены. Если бы мы купили по одному экземпляру каждого товара в корзине, какова была бы цена?

Используем summingDouble(), чтобы получить предложение по цене:

Double sumOfPrices = products.stream().collect(Collectors.summingDouble(Product::getPrice));
System.out.println("The total sum of prices: " + sumOfPrices);

Получаем:

The total sum of prices: 47.69

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

List<Double> productPrices = new ArrayList<>();
for(Product product : products){
    productPrices.add(product.getPrice() * product.getQuantity());
}

Double sumOfPrices = productPrices.stream().collect(Collectors.summingDouble(Double::doubleValue));
System.out.println("Sum of all product prices : " + sumOfPrices);

В итоге получаем:

Sum of all product prices : 1411.67

Collectors.summingLong()

Третий и последний метод из группы методов суммирования – summingLong(). Этот метод, как и предыдущие два, возвращает коллекцию, которая выдаёт сумму функции с длинным значением, применённой к входным элементам. Если элементы отсутствуют, результат равен 0:

List<Long> numbers = Arrays.asList(23L, 11L, 13L, 49L, 7L);
Long sum = numbers.stream().collect(Collectors.summingLong(Long::longValue));
System.out.println(sum);

Вывод:

103

Наконец, наше поле productNumber имеет тип Long. Оказывается, числа были тщательно подобраны для кодирования секретного сообщения после разделения и преобразования из десятичного в ASCII. Мы просуммируем значения и напишем пользовательскую вспомогательную функцию для декодирования сообщения:

Long productNumbers = products.stream().collect(Collectors.summingLong(Product::getProductNumber));
System.out.println(productNumbers);
System.out.println(decodeMessage(productNumbers));

Метод decodeMessage() берёт тип Long, разбивает его и рассматривает каждые два символа как десятичное представление символа, прежде чем добавлять символы и возвращать сообщение:

public static String decodeMessage(Long encodedMessage) {
    String message = String.valueOf(encodedMessage);
    String[] characters = message.split("");
    StringBuilder result = new StringBuilder();

    for (int i = 1; i < characters.length; i+=2) {
        result.append(Character.toString(Integer.parseInt(characters[i-1]+characters[i])));
    }

    return result.toString();
}

Класс, посмотрим на результат:

74658665
JAVA

Это не какое-то мега секретное послание, но всё же послание.

Заключение

В этом уроке мы рассмотрели коллекции суммирования и изучили методы summingint(), summingDouble() и summinglong().

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

Ответить