Skip to main content

Somar uma lista de números em Java

Escrito por
Java engineering feature

22 de maio de 2020

0 minutos de leitura

De vez em quando, preciso fazer algumas coisas básicas em Java e me pergunto qual é a melhor maneira de fazê-las. Isso aconteceu comigo há alguns dias! Eu só precisava somar uma lista de números e descobri que há várias maneiras — trocadilho intencional — de fazer isso.

A abordagem tradicional

Podemos criar um loop simples para fazer isso. Estou usando Java 11, então me perdoe se você estiver usando, por exemplo, Java 8, e List.of e var não funcionarem no seu caso. Ainda assim, acredito que você vai entender a ideia.

var listOfNumbers = List.of(1,2,3,4,5,6,7,8,9,10);

var sum = 0;
for (int i = 0; i < listOfNumbers.size() ; i++) {
    sum += listOfNumbers.get(i);
}

Obviamente, desde o Java 5 temos melhorias nos loops, então posso reescrever o mesmo código assim.

var listOfNumbers = List.of(1,2,3,4,5,6,7,8,9,10);

var sum = 0;
for (int number : listOfNumbers) {
    sum += number;
}

A diferença é sutil. Ainda assim, o código fica mais expressivo, pois diz algo como: "para cada number vindo de listOfNumbers, quero fazer o seguinte...".

A abordagem com Java Stream

Quem me conhece sabe que, durante a faculdade, fui doutrinado na programação com Haskell. Isso significa que tenho muito apreço pela programação funcional pura. Não que Java dê conta disso ?, mas parte da expressividade da programação funcional está disponível por meio da API Stream.

Com a API Stream do Java, podemos executar o modelo de programação MapReduce. Para o problema que estou tentando resolver aqui, não preciso mapear os valores, pois os números continuarão iguais. Mas preciso reduzir a lista a um único número: a soma.

Collect

Em provavelmente 99% dos casos, usamos a função collect com o coletor padrão toList() para transformar o stream novamente em uma lista. Algo parecido com isto:

       var time2ToList = listOfNumbers.stream()
                .map(i -> i * 2)
                .collect(Collectors.toList());

Mas a vida não se resume a coletar um stream de volta em uma lista. Ao explorar a biblioteca Collectors, você encontra funções como summingInt(), summingDouble() e summingLong(). Você pode usar essas funções para coletar (ou reduzir) a lista e obter a soma.

A função summmingInt exige uma função que transforme a entrada em um int. Neste caso, posso simplesmente usar a "função identidade". A função i -> i é suficiente.

        var listOfNumbers = List.of(1,2,3,4,5,6,7,8,9,10);
        var sum = listOfNumbers.stream()
                    .collect(Collectors.summingInt(i -> i));

Essa função identidade pode parecer boba, então você pode usar Integer.intValue() no lugar.

       var listOfNumbers = List.of(1,2,3,4,5,6,7,8,9,10);
       var sum = listOfNumbers.stream()
                .collect(Collectors.summingInt(Integer::intValue));

Quando faço isso, minha IDE — no meu caso, o IntelliJ IDEA — sugere que eu refatore o código e use a função mapToInt(), como mostrado abaixo:

       var listOfNumbers = List.of(1,2,3,4,5,6,7,8,9,10);
       var sum = listOfNumbers.stream()
                .mapToInt(Integer::intValue).sum();

Tecnicamente, o que fazemos aqui é mapear cada item para um int — que já é o tipo dele ¯\(ツ)/¯ — e reduzi-lo usando a função sum().

Isso fica mais claro quando você observa os tipos inferidos. Simplesmente não é possível ter uma lista de tipos primitivos. Portanto, a lista contém valores Integer (o objeto). Isso significa que cada item da lista precisa voltar a ser o tipo primitivo int para que seja possível usar sum(). O exemplo anterior, com a função identidade no coletor, funciona por causa do unboxing do Java.

Se você prefere usar listas de tipos primitivos em Java, sugiro conhecer a biblioteca Eclipse Collections.

Reduce

No Java, a redução é feita com algumas funções da API Stream. Além de collect(), também existe a função de nome bastante óbvio: reduce().

       var listOfNumbers = List.of(1,2,3,4,5,6,7,8,9,10);
       var sum = listOfNumbers.stream()
                .reduce(0 , (num1, num2) -> num1 + num2);

Neste caso, a função reduce recebe um valor inicial e uma expressão lambda BiFunction. A BiFunction é aplicada ao valor inicial e ao primeiro número; o resultado da função é aplicado ao segundo número, e assim por diante.

O código acima faz algo como isto: 0 + 1; 1 + 2; 3 + 3; 6 + 4; e assim por diante…

Agora, você pode omitir o valor inicial 0. No entanto, nesse caso, a função reduce retorna um Optional, pois a lista que ela tenta reduzir pode estar vazia.

Conclusão

Como você pode ver, há várias maneiras de resolver esse problema. Sem dúvida, as pessoas vão encontrar maneiras ainda mais exóticas de fazer isso. Minha opção favorita é reduce(). Para mim, essa é a solução mais expressiva e pura em Java. Só quero reduzir uma lista a um único número, sem precisar me preocupar com as conversões de tipos boxed para tipos primitivos. Além disso, posso reutilizar essa abordagem quando precisar reduzir uma lista de outros tipos, escrevendo uma função lambda de redução que atenda às minhas necessidades.

Publicado em: