Jak zsumować listę liczb całkowitych strumieniami Java?

364

Chcę podsumować listę liczb całkowitych. Działa w następujący sposób, ale składnia nie wydaje się właściwa. Czy można zoptymalizować kod?

Map<String, Integer> integers;
integers.values().stream().mapToInt(i -> i).sum();
Membersound
źródło
5
„ale składnia nie wydaje się właściwa” Co sprawia, że ​​tak myślisz? To jest zwykły idiom. Może chcesz użyć, mapToLongaby uniknąć przepełnienia, w zależności od wartości, jakie może mieć mapa.
Alexis C.,
3
@JBNizet Osobiście uważam za i -> ibardzo jasne. Cóż, tak, musisz wiedzieć, że wartość zostanie automatycznie rozpakowana, ale jest to prawdą, ponieważ Java 5 ...
Alexis C.
4
@AlexisC. jest zrozumiałe, ponieważ jest przekazywane do mapToInt () i ponieważ jestem doświadczonym programistą. Ale ja -> ja, bez kontekstu, wygląda jak noop. Integer :: intValue jest bardziej szczegółowe, ale wyraźnie wyjaśnia operację rozpakowywania.
JB Nizet
1
@JBNizet Ludzie, którzy wywołują metodę foo(int i), nie piszą za foo(myInteger.intValue());każdym razem, gdy ją wywołują (a przynajmniej nie oczekuję !!). Zgadzam się z tobą, co Integer::intValuejest bardziej wyraźne, ale myślę, że to samo dotyczy tutaj. Ludzie powinni się tego nauczyć raz, a potem skończysz :-). To nie jest tak, jakby to było jakieś magiczne zaciemnienie.
Alexis C.,
4
@JB Nizet: i -> iwygląda na to , że nie ma op, a koncepcyjnie jest to brak op. Jasne, pod maską Integer.intValue()zostaje wywołane, ale jeszcze głębiej pod maską, że metody są wprowadzane, aby stać się dokładnie tym, czego nie ma w kodzie źródłowym. Integer::intValuema tę zaletę, że nie tworzy syntetycznej metody w kodzie bajtów, ale nie jest to tym, co powinno decydować o sposobie organizacji kodu źródłowego.
Holger

Odpowiedzi:

498

To zadziała, ale i -> iwykonuje automatyczne rozpakowywanie, dlatego „wydaje się” dziwne. Oba z poniższych sposobów będą działać i lepiej wyjaśnić, co kompilator robi pod maską, używając oryginalnej składni:

integers.values().stream().mapToInt(i -> i.intValue()).sum();
integers.values().stream().mapToInt(Integer::intValue).sum();
Necreaux
źródło
2
Co jeśli mamy BigInteger :)?
GOXR3PLUS
13
Jedną z prostych opcji jestBigDecimal sum = numbers.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
Matthew
158

Sugeruję jeszcze 2 opcje:

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

Drugi używa Collectors.summingInt()kolektora, jest też summingLong()kolektor, którego można użyć mapToLong.


I trzecia opcja: Java 8 wprowadza bardzo skuteczny LongAdderakumulator zaprojektowany w celu przyspieszenia podsumowania w równoległych strumieniach i środowiskach wielowątkowych. Oto przykład użycia:

LongAdder a = new LongAdder();
map.values().parallelStream().forEach(a::add);
sum = a.intValue();
Alex Salauyou
źródło
86

Z dokumentów

Operacje redukcji Operacja redukcji (zwana także foldem) pobiera sekwencję elementów wejściowych i łączy je w jeden wynik podsumowania poprzez wielokrotne stosowanie operacji łączenia, takiej jak znalezienie sumy lub maksimum zbioru liczb lub gromadzenie elementów w lista. Klasy strumieni mają wiele form ogólnych operacji redukcji, zwanych zmniejszeniami () i collect (), a także wiele specjalistycznych form redukcji, takich jak sum (), max () lub count ().

Oczywiście takie operacje można łatwo wdrożyć jako proste sekwencyjne pętle, jak w:

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

Istnieją jednak dobre powody, aby preferować operację zmniejszania zamiast akumulacji mutacyjnej, takiej jak powyżej. Redukcja jest nie tylko „bardziej abstrakcyjna” - działa na strumieniu jako całości, a nie na pojedynczych elementach - ale odpowiednio skonstruowana operacja redukcji jest z natury możliwa do zrównoleglenia, pod warunkiem, że funkcje zastosowane do przetwarzania elementów są skojarzone i bezpaństwowiec. Na przykład, biorąc pod uwagę strumień liczb, dla którego chcemy znaleźć sumę, możemy napisać:

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

lub:

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

Te operacje redukcji mogą przebiegać bezpiecznie równolegle, prawie bez modyfikacji:

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

Tak więc dla mapy użyłbyś:

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

Lub:

integers.values().stream().reduce(0, Integer::sum);
J Atkin
źródło
2
To, co ma PO, jest znacznie lepsze, a także jaśniejsze. Ten kod wiązałby się z całą niechęcią do rozpakowywania i operacji bokserskich.
JB Nizet
1
@JBNizet Chyba że analiza ucieczki eliminuje boks. Musisz spróbować, aby zobaczyć, czy to możliwe.
Peter Lawrey,
6
(x, y) -> x + y musi rozpakować xiy, zsumować je, a następnie zaznaczyć wynik. I zacznij od nowa, aby dodać wynik do następnego elementu strumienia i od nowa.
JB Nizet
3
Liczba całkowita :: suma cierpi z powodu tego samego problemu. A jeśli użyjesz mapToInt (), aby mieć IntStream, wywołanie sum () na nim jest prostsze niż wywołanie redukcji ().
JB Nizet
3
Zobacz docs.oracle.com/javase/8/docs/api/java/lang/… . Dwa argumenty Integer.sum () są typu int. Tak więc dwie liczby całkowite ze strumienia muszą zostać rozpakowane, aby mogły zostać przekazane jako argumenty do metody. Metoda zwraca int, ale redukcja () przyjmuje jako argument argument BinaryOperator <Integer>, który w ten sposób zwraca liczbę całkowitą. Wynik sumy należy więc zapisać w polu „Liczba całkowita”.
JB Nizet,
28

Możesz użyć metody zmniejszania:

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

lub

long sum = result.stream().map(e -> e.getCreditAmount()).reduce(0L, Integer::sum);
Saeed Zarinfam
źródło
9
Jest już taki akumulator int, to jestInteger::sum
Alex Salauyou
1
Długo wracasz, więc byłoby lepiej Long::sumniż Integer::sum.
Andrei Damian-Fekete,
16

Możesz użyć reduce()do zsumowania listy liczb całkowitych.

int sum = integers.values().stream().reduce(0, Integer::sum);
Johnson Abraham
źródło
11

Możesz użyć metody Collect, aby dodać listę liczb całkowitych.

List<Integer> list = Arrays.asList(2, 4, 5, 6);
int sum = list.stream().collect(Collectors.summingInt(Integer::intValue));
Ashish Jha
źródło
6

Byłby to najkrótszy sposób na podsumowanie inttypu tablica (dla longtablicy LongStream, dla doubletablicy DoubleStreami tak dalej). StreamJednak nie wszystkie pierwotne typy całkowite lub zmiennoprzecinkowe mają implementację.

IntStream.of(integers).sum();
Sachith Dickwella
źródło
Niestety nie mamy żadnej int-array. Więc IntStream.of()nie zadziała dla tego problemu, chyba że stworzymy coś tak upiornego:IntStream.of( integers.values().stream().mapToInt( Integer::intValue ).toArray() ).sum();
Kaplan,
Nie ma potrzeby, to wystarczy integers.values().stream().mapToInt( Integer::intValue ).sum().
Sachith Dickwella,
3

Może to pomóc tym, którzy mają obiekty na liście.

Jeśli masz listę obiektów i chcesz zsumować określone pola tego obiektu, skorzystaj z poniższej listy.

List<ResultSom> somList = MyUtil.getResultSom();
BigDecimal result= somList.stream().map(ResultSom::getNetto).reduce(
                                             BigDecimal.ZERO, BigDecimal::add);
itro
źródło
Dzięki temu pomógł mi w jednym z moich scenariuszy
A_01
1

Zadeklarowałem listę liczb całkowitych.

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

Możesz spróbować użyć tych różnych sposobów poniżej.

Za pomocą mapToInt

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

Za pomocą summarizingInt

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

Za pomocą reduce

int sum = numberList.stream().reduce(Integer::sum).get().intValue();
JDGuide
źródło
-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);
Ranjit Soni
źródło
-1

Omówiono większość aspektów. Ale może istnieć wymóg znalezienia agregacji innych typów danych oprócz Integer, Long (dla których już istnieje specjalistyczna obsługa strumienia). Na przykład stram z BigInteger W przypadku takiego typu możemy użyć operacji zmniejszania jak

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

ManojG
źródło