Java e OO

Calculando a média numérica com Stream do Java 8

Você já percebeu a quantidade de código que é necessário para calcular a média de uma lista de valores em Java? Bem, era necessário!

Dentre as várias novidades do Java 8, o stream trouxe muitas formas diferentes e fáceis de realizar algumas operações comuns.

Neste artigo você vai aprender a fazer algo simples, calcular a média de valores armazenados em uma coleção, mas usando o stream e em uma linha de código!

Como o título diz, o resultado é simples, calcular a média numérica de uma lista de valores. Para isso, vamos criar um programa que recebe e calcula a média de gols de um jogador de futebol.

Os requisitos do programa são:

  • Receber do usuário o nome do jogador
  • Perguntar ao usuário se ele deseja adicionar algum gol para o jogador
  • Caso ele decida adicionar algum gol, pedir a quantidade de gols para cada partida
  • Ficar em um loop recebendo os gols até o usuário decidir finalizar o cadastro
  • Imprimir a média de gols do jogador

Para reproduzir esse exemplo, é necessário ter o Java 8 instalado no seu computador, não irá funcionar com versões anteriores. Portanto, baixe a nova versão no site da Oracle clicando aqui.

Vamos criar a classe CalculadoraMediaGols com o método main abaixo:

public static void main(String[] args) {
    System.out.println("#### Média de gols ####");
    System.out.println();

    String nome;
    List<Integer> golsPorPartida;

    try (Scanner entrada =  new Scanner(System.in)) {
        System.out.print("Informe o nome do jogador: ");
        nome = entrada.nextLine();
        
        String continuar;
        System.out.print("Você gostaria de adicionar gols a este jogador? (s/n): ");
        continuar = entrada.nextLine();
        
        int partida = 1;
        golsPorPartida = new ArrayList<>();
        while ("s".equalsIgnoreCase(continuar)) {
            System.out.printf("Quantos gols %s fez na partida %d: ", nome, partida);
            int golsNaPartida = entrada.nextInt();
            golsPorPartida.add(golsNaPartida);
            
            partida++;
            
            System.out.print("Deseja continuar (s/n): ");
            continuar = entrada.next();
        }
    }

    imprimirMediaDeGols(nome, golsPorPartida);
}

private static void imprimirMediaDeGols(String nome, List<Integer> golsPorPartida) {

    // Realizar o cálculo da média e imprimir na tela

}

O código acima é simples de entender, ele começa imprimindo algumas mensagens na tela, criando algumas variáveis e então pede o nome do jogador.

Logo em seguida perguntamos ao usuário do nosso programa se ele deseja adicionar gols ao jogador. Pode ser que aquele jogador esteja machucado ou não fez nenhum gol.

Caso ele decida adicionar, um loop é iniciado onde será informado a quantidade de gols por partida, até encerrar o cadastro.

Perceba que quando o cadastro é finalizado, teremos um ArrayList de inteiros com os gols do jogador por partida.

É agora que começa nossa diversão! Calcular a média de gols desse jogador.

Vamos primeiro calcular a média como possivelmente faríamos com Java antes da versão 8. Veja o código abaixo:

private static void imprimirMediaDeGols(String nome, List<Integer> golsPorPartida) {
    double totalGols = 0;
    for (Integer golsNaPartida : golsPorPartida) {
      totalGols += golsNaPartida;
    }
      
    double media = totalGols / golsPorPartida.size();

    System.out.printf("\nO %s fez uma média de %.2f gols por jogo", nome, media);
}

Repare que primeiro criamos uma variável temporária para somar a quantidade de gols. Depois um loop para somar os gols. Em seguida podemos fazer finalmente o cálculo da média, que é o total dividido pela quantidade de jogos, no caso o tamanho da lista.

Mas espere, existe um problema ali! E se o usuário não cadastrar nenhum gol ao jogador? O tamanho da lista será zero! E não é possível dividir um número por zero, resultando em um NaN. Not a Number – Não é um número.

Faça o teste, quando o programa perguntar se deseja adicionar gols ao jogador, diga que não. A saída será:

O CR7 fez uma média de NaN gols por jogo

Então precisamos adicionar ao método uma condicional, ficando como abaixo:

private static void imprimirMediaDeGols(String nome, List<Integer> golsPorPartida) {
    double totalGols = 0;
    for (Integer golsNaPartida : golsPorPartida) {
        totalGols += golsNaPartida;
    }
    
    double media = 0;
    if (!golsPorPartida.isEmpty()) {
        media = totalGols / golsPorPartida.size();
    } 

    System.out.printf("\nO %s fez uma média de %.2f gols por jogo", nome, media);
}

Agora sim, nosso método calcula a média caso existam gols cadastrados ou imprime zero.

Gastamos 9 linhas de código (contando a linha em branco) para fazer esse cálculo. Tudo bem, poderia ter tirado as chaves do for e if para diminuir, apesar de eu não gostar, mas mesmo assim ainda teríamos 7 linhas.

Eu vou te mostrar como reduzir esse cálculo para apenas 1 linha, isso mesmo 1 linha de código! Fique comigo até o final que quem viver verá! :)

Mas antes de colocar apenas uma linha direto, vou te mostrar passo a passo como chegar a ela.

O primeiro ponto a se destacar é o novo método na coleção que retorna um Stream, que também faz parte da nova API do Java 8. Veja abaixo:

Stream<Integer> stream = golsPorPartida.stream();

Você pode pensar em um stream como um iterator, ou seja, um objeto que consegue navegar pelos valores da coleção, e veja que ele é um stream de Integer, o mesmo do List.

Nele existe um método, o mapToDouble(), que irá retornar um DoubleStream; que é um stream especial, onde teremos alguns métodos especiais para reduzir nossa coleção a um valor só! No nosso caso a média.

Mas para gerarmos o DoubleStream precisamos dizer como converter o Integer para Double.

Vamos usar outro recurso especial do Java 8, o method reference ou referência a métodos. Com ele podemos chamar um método, em cada elemento da coleção, para converter o Integer em Double. Veja o código abaixo:

Stream<Integer> stream = golsPorPartida.stream();
DoubleStream doubleStream = stream.mapToDouble(Integer::doubleValue);

No DoubleStream existem alguns métodos bem legais para reduzir a coleção, como max(), min(), sum() e também o average().

Portanto, para calcular a média, iremos usar o average(), mas observe que ele não retorna um double diretamente. Veja abaixo que é retornado um OptionalDouble.

Stream<Integer> stream = golsPorPartida.stream();
DoubleStream doubleStream = stream.mapToDouble(Integer::doubleValue);
OptionalDouble optionalDouble = doubleStream.average();

O OptionalDouble é um objeto que irá nos ajudar a evitar escrever a condição caso não exista a média. Ao invés do if, podemos usar o método orElse(). Ficando assim:

Stream<Integer> stream = golsPorPartida.stream();
DoubleStream doubleStream = stream.mapToDouble(Integer::doubleValue);
OptionalDouble optionalDouble = doubleStream.average();
double media = optionalDouble.orElse(0.0);

Ou seja, se não existir a média, caso a coleção esteja vazia, será retornado 0.0. Existem outros métodos também, como o orElseThrow() que você pode usar para lançar uma exceção.

Como te prometi, vamos reduzir esse código a apenas uma linha. Basta colocar as chamadas em cascata, veja como é simples:

double media = golsPorPartida.stream().mapToDouble(Integer::doubleValue).average().orElse(0.0);

Assista ao vídeo com a explicação passo a passo do exemplo, que você vai conseguir entender muito melhor.

Você já conhecia o stream? Comente sobre o que achou da vídeo aula e artigo. :)

Graduado em Engenharia Elétrica pela Universidade Federal de Uberlândia e detentor das certificações LPIC-1, SCJP e SCWCD.

Olá,

o que você achou deste conteúdo? Conte nos comentários.

Junte-se a mais de 100.000 pessoas

Entre para nossa lista e receba conteúdos exclusivos e com prioridade

Você se Inscreveu com Sucesso!