Как суммировать список целых чисел с потоками Java?


365

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

Map<String, Integer> integers;
integers.values().stream().mapToInt(i -> i).sum();

5
«но синтаксис не кажется правильным» Что заставляет вас так думать? Это обычная идиома. Может быть, вы хотите использовать, mapToLongчтобы избежать переполнения, в зависимости от значений, которые может иметь ваша карта.
Алексис С.

3
@JBNizet Я нахожу i -> iочень ясно, лично. Ну, да, вам нужно знать, что значение будет автоматически распаковано, но это правда, начиная с Java 5 ...
Алексис С.

4
@AlexisC. это понятно, потому что оно передано в mapToInt (), и потому что я опытный разработчик. Но я -> я, без контекста, выглядит как шпион. Integer :: intValue более многословен, но делает операцию распаковки явной.
JB Низет

1
@JBNizet Люди, которые вызывают метод foo(int i), не пишут foo(myInteger.intValue());каждый раз, когда они его вызывают (или, по крайней мере, я ожидаю, что нет !!). Я согласен с вами, что Integer::intValueболее четко, но я думаю, что то же самое применимо и здесь. Люди должны просто изучить это один раз, и тогда вы закончите :-). Это не так, как если бы это было какое-то волшебное запутывание.
Алексис С.

4
@JB Nizet: хорошо, i -> iвыглядит как не-оп и концептуально, это не является не-оп. Конечно, Integer.intValue()изнутри вызывается, но еще глубже скрывается, что эти методы становятся встроенными, чтобы в точности стать неактивными, как это выглядит в исходном коде. Integer::intValueимеет бонусный момент: не нужно создавать синтетический метод в байт-коде, но это не то, что должно определять ваше решение о том, как организовать исходный код.
Хольгер

Ответы:


499

Это сработает, но i -> iпроисходит автоматическая распаковка, поэтому это «странно». Любое из следующего будет работать и лучше объяснить, что компилятор делает под капотом с вашим оригинальным синтаксисом:

integers.values().stream().mapToInt(i -> i.intValue()).sum();
integers.values().stream().mapToInt(Integer::intValue).sum();

2
Что если у нас есть BigInteger :)?
GOXR3PLUS

13
Один простой вариантBigDecimal sum = numbers.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
Матфея

158

Я предлагаю еще 2 варианта:

integers.values().stream().mapToInt(Integer::intValue).sum();
integers.values().stream().collect(Collectors.summingInt(Integer::intValue));

Второй использует Collectors.summingInt()коллектор, также есть summingLong()коллектор, с которым вы будете использовать mapToLong.


И третий вариант: Java 8 представляет очень эффективный LongAdderаккумулятор, предназначенный для ускорения суммирования в параллельных потоках и многопоточных средах. Вот пример использования:

LongAdder a = new LongAdder();
map.values().parallelStream().forEach(a::add);
sum = a.intValue();

86

Из документов

Операции сокращения Операция сокращения (также называемая сгибанием) берет последовательность элементов ввода и объединяет их в единый итоговый результат путем многократного применения операции объединения, такой как поиск суммы или максимума набора чисел или накопление элементов в список. Классы потоков имеют несколько форм общих операций редукции, которые называются redu () и collect (), а также несколько специализированных форм редукции, таких как sum (), max () или count ().

Конечно, такие операции могут быть легко реализованы в виде простых последовательных циклов, например:

int sum = 0;
for (int x : numbers) {
   sum += x;
}

Тем не менее, есть веские причины, чтобы предпочесть операцию сокращения, а не мутирующую аккумуляцию, такую ​​как описанная выше. Сокращение не только «более абстрактно» - оно работает с потоком в целом, а не с отдельными элементами, - но правильно сконструированная операция сокращения по своей сути распараллеливается, если функции, используемые для обработки элементов, являются ассоциативными. и без гражданства. Например, учитывая поток чисел, для которого мы хотим найти сумму, мы можем написать:

int sum = numbers.stream().reduce(0, (x,y) -> x+y);

или:

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

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

int sum = numbers.parallelStream().reduce(0, Integer::sum);

Итак, для карты вы бы использовали:

integers.values().stream().mapToInt(i -> i).reduce(0, (x,y) -> x+y);

Или:

integers.values().stream().reduce(0, Integer::sum);

2
То, что есть у ОП, намного лучше, а также понятнее. Этот код будет включать в себя целый ряд операций по распаковке и упаковке.
Дж. Б. Низет

1
@JBNizet Если анализ побега не устраняет бокс. Вы должны попробовать это, чтобы увидеть, если это возможно.
Питер Лори

6
(x, y) -> x + y необходимо распаковать x и y, суммировать их, а затем поставить в квадрат результат. И начните снова, чтобы добавить результат со следующим элементом потока, и снова и снова.
JB Низет

3
Integer :: sum страдает от той же проблемы. И если вы используете mapToInt (), чтобы иметь IntStream, вызов sum () для него более прост, чем вызову redu ().
Дж. Б. Низет

3
См. Docs.oracle.com/javase/8/docs/api/java/lang/… . Два аргумента Integer.sum () имеют тип int. Таким образом, два целых числа из потока должны быть распакованы для передачи в качестве аргументов методу. Метод возвращает целое число, но метод Reduce () принимает BinaryOperator <Integer> в качестве аргумента, который, таким образом, возвращает Integer. Таким образом, результат суммы должен быть упакован в целое число.
JB Низет

28

Вы можете использовать метод уменьшить:

long sum = result.stream().map(e -> e.getCreditAmount()).reduce(0L, (x, y) -> x + y);

или

long sum = result.stream().map(e -> e.getCreditAmount()).reduce(0L, Integer::sum);

9
Уже есть такой аккумулятор для int, этоInteger::sum
Alex Salauyou

1
Вы возвращаетесь долго, так что это будет лучше, Long::sumчем Integer::sum.
Андрей Дамиан-Фекете

16

Вы можете использовать reduce()для суммирования списка целых чисел.

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

11

Вы можете использовать метод сбора, чтобы добавить список целых чисел.

List<Integer> list = Arrays.asList(2, 4, 5, 6);
int sum = list.stream().collect(Collectors.summingInt(Integer::intValue));

6

Это был бы самый короткий способ суммировать intмассив типов (для longмассива LongStream, для doubleмассива DoubleStreamи т. Д.). Однако не все примитивные целочисленные типы или типы с плавающей запятой имеют Streamреализацию.

IntStream.of(integers).sum();

К сожалению, у нас нет никакого int-массива. Так IntStream.of()что не будет работать для этой проблемы, если мы не сделаем что-то похожее на привидение:IntStream.of( integers.values().stream().mapToInt( Integer::intValue ).toArray() ).sum();
Kaplan

Нет необходимости, этого будет достаточно integers.values().stream().mapToInt( Integer::intValue ).sum().
Сачит Диквелла

3

Пусть это поможет тем, у кого есть объекты в списке.

Если у вас есть список объектов и вы хотите суммировать конкретные поля этого объекта, используйте ниже.

List<ResultSom> somList = MyUtil.getResultSom();
BigDecimal result= somList.stream().map(ResultSom::getNetto).reduce(
                                             BigDecimal.ZERO, BigDecimal::add);

Спасибо, это помогло мне в одном из моих
сценариев

1

Я объявил список целых чисел.

ArrayList<Integer> numberList = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4, 5));

Вы можете попробовать использовать эти различные способы ниже.

С помощью mapToInt

int sum = numberList.stream().mapToInt(Integer::intValue).sum();

С помощью summarizingInt

int sum = numberList.stream().collect(Collectors.summarizingInt(Integer::intValue)).getSum();

С помощью reduce

int sum = numberList.stream().reduce(Integer::sum).get().intValue();

-1
class Pojo{
    int num;

    public Pojo(int num) {
        super();
        this.num = num;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }
}

List<Pojo> list = new ArrayList<Pojo>();
            list.add(new Pojo(1));
            list.add(new Pojo(5));
            list.add(new Pojo(3));
            list.add(new Pojo(4));
            list.add(new Pojo(5));

            int totalSum = list.stream().mapToInt(pojo -> pojo.getNum()).sum();
            System.out.println(totalSum);

-1

Большинство аспектов покрыты. Но может возникнуть необходимость найти агрегацию других типов данных, кроме Integer, Long (для которых уже есть поддержка специализированных потоков). Например, Stram с BigInteger. Для такого типа мы можем использовать операцию

list.stream (). Reduce ((bigInteger1, bigInteger2) -> bigInteger1.add (bigInteger2))

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.