Primeiras aulas do curso Java 8: Tire proveito dos novos recursos da linguagem

Java 8: Tire proveito dos novos recursos da linguagem

Default Methods - Default Methods

O Java 8

As tarefas mais comuns de um programador podem ser desafiadoras no Java. O motivo? A sintaxe com mais de 20 anos, tornando-a uma linguagem burocrática. Felizmente isso mudou significativamente com o Java 8. Um exemplo? Ordenação de objetos.

Vamos usar o Eclipse nos nossos exemplos. Você pode utilizar qualquer outra IDE, ou até mesmo a linha de comando. O antigo Eclipse Kepler 4.3 possui suporte ao Java 8 via download, a partir do Eclipse Luna 4.4, esse suporte já vem ativado. Lembre-se de verificar se você tem o Java 8 instalado, indo ao console/terminal e digitando java -version. Deve sair 1.8.0. Caso contrário, atualize a versão do seu java.

No eclipse, crie um novo projeto chamado Java8 e, através das propriedades do projeto, escolha a opção Java Compiler e ative a versão 8. Se ela não está disponível e você tem certeza que instalou o Java 8, basta adicionar esse JDK em Windows, Preferences, Java, Installed JREs.

Em uma nova classe OrdenaStrings, crie o método main e vamos fazer uma lista de strings e trabalhar com ele sem nenhum dos novos recursos da linguagem:

List<String> palavras = new ArrayList<>();
palavras.add("alura online");
palavras.add("casa do código");
palavras.add("caelum");

Vale lembrar que podemos criar uma lista de objetos diretamente via Arrays.asList, fazendo List<String> palavras = Arrays.asList("", "", ...). A diferença é que não se pode mudar a quantidade de elementos de uma lista devolvida por esse método.

Como fazemos para ordenar essa lista? Podemos fazer isso sem usar nenhuma novidade: com o Collections.sort:

Collections.sort(palavras);
System.out.println(palavras);

E se quisermos ordenar essas palavras em uma ordem diferente? Por exemplo, pela ordem do tamanho das palavras. Nesse caso, utilizaremos um Comparator. Podemos criá-la como uma outra classe, por enquanto apenas o esqueleto:


class ComparadorDeStringPorTamanho implements Comparator<String> {

    public int compare(String s1, String s2) {
        return 0;
    }

}

O que preencher aí dentro? Se você lembrar, o contrato da interface Comparator diz que devemos devolver um número negativo se o primeiro objeto for menor que o segundo, um número positivo caso contrário e zero se forem equivalentes. Esse "maior", "menor" e "equivalente" é algo que nós decidimos. No nosso caso, vamos dizer que uma String é "menor" que outra se ela tiver menos caracteres. Então podemos fazer:

class ComparadorDeStringPorTamanho implements Comparator<String> {
    public int compare(String s1, String s2) {
        if(s1.length() < s2.length()) 
            return -1;
        if(s1.length() > s2.length()) 
            return 1;
        return 0;
    }
}

E, para ordenar com esse novo critério de comparação, podemos fazer:

Comparator<String> comparador = new ComparadorDeStringPorTamanho();
Collections.sort(palavras, comparador);

Até aqui, nenhuma novidade. No decorrer do curso, você verá como esse código ficará muito, muito menor, mais sucinto e expressivo com cada recurso que formos estudar do Java 8. Vamos ao primeiro deles. Em vez de usar o Collections.sort, podemos invocar essa operação na própria List! Veja:

Comparator<String> comparador = new ComparadorDeStringPorTamanho();
palavras.sort(comparador);

Parece pouco, mas há muita coisa por trás. Em primeiro lugar, esse método sort não existia antes na interface List, nem em suas mães (Collection e Iterable).

Será então que simplesmente adicionaram um novo método? Se tivessem feito assim, haveria um grande problema: todas as classes que implementam List parariam de compilar, pois não teriam o método sort. E há muitas, muitas classes que implementam essas interfaces básicas do Java. Há implementações no Hibernate, no Google Collections e muito mais.

Default Methods

Para evitar essa quebra, o Java 8 optou por criar um novo recurso que possibilitasse adicionar métodos em interfaces e implementá-los ali mesmo! Se você abrir o código fonte da interface List, verá esse método:

    default void sort(Comparator<? super E> c) {
        Collections.sort(this, c);
    }

É um default method! Um método de interface que você não precisa implementar na sua classe se não quiser, pois você terá já essa implementação default. Repare que ele simplesmente delega a invocação para o bom e velho Collections.sort, mas veremos que outros métodos fazem muito mais.

Default methods foi uma forma que o Java encontrou para evoluir interfaces antigas, sem gerar incompatibilidades. Não é uma novidade da linguagem: Scala, C# e outras possuem recursos similares e até mais poderosos. E repare que é diferente de uma classe abstrata: em uma interface você não pode ter atributos de instância, apenas esses métodos que delegam chamadas ou trabalham com os próprios métodos da interface.

foreach, Consumer e interfaces no java.util.functions

Vamos a um outro método default adicionado as coleções do Java: o forEach na interface Iterable. Como Iterable é mãe de Collection, temos acesso a esse método na nossa lista.

Se você abrir o JavaDoc ou utilizar o auto complete do Eclipse, verá que List.forEach recebe um Consumer, que é uma das muitas interfaces do novo pacote java.util.functions. Então vamos criar um consumidor de String:

class ConsumidorDeString implements Consumer<String> {
    public void accept(String s) {
        System.out.println(s);
    }
}

E podemos passar uma instância dessa para o forEach:

Consumer<String> consumidor = new ConsumidorDeString();
palavras.forEach(consumidor);

Interessante? Ainda não muito. Talvez fosse mais direto e simples escrever um for(String s : lista).

Default methods é o primeiro recurso que conhecemos. Sim, é bastante simples e parece não trazer grandes melhorias. O segredo é utilizá-los junto com lambdas, que você verá a seguir, e trará um impacto significativo para o seu código.

Que venham os lambdas! - Que venham os lambdas!

Melhorando com classes anônimas

Vamos retomar o nosso forEach, ele precisa da classe que implementa Consumer:

class ConsumidorDeString implements Consumer<String> {
    public void accept(String s) {
        System.out.println(s);
    }
}

E a invocação:

Consumer<String> consumidor = new ConsumidorDeString();
palavras.forEach(consumidor);

Se você já está acostumado com Java há mais tempo, sabe que nesses casos não criamos uma classe isolada. Fazemos tudo ao mesmo tempo, criando a classe e instanciando-a:

Consumer<String> consumidor = new Consumer<String>() {
    public void accept(String s) {
        System.out.println(s);
    }
};
palavras.forEach(consumidor);

São as chamadas classes anônimas, que usamos com frequência para implementar listeners e callbacks que não terão reaproveitamento.

Poderíamos até mesmo evitar a criação da variável consumidor, passando a classe anônima diretamente para o forEach:

palavras.forEach(new Consumer<String>() {
    public void accept(String s) {
        System.out.println(s);
    }
});

Quando começamos a aprender Java, essa sintaxe pode intimidar. Ela aparece com frequência, em especial nesses casos onde a implementação é curta e simples.

Lambda para simplificar

Tendo essas dificuldade e verbosidade da sintaxe das classes anônimas em vista, o Java 8 traz uma nova forma de implementar essas interfaces ainda mais sucinta. É a sintaxe do lambda. Em vez de escrever a classe anônima, deixamos de escrever alguns itens que podem ser inferidos.

Como essa interface só tem um método, não precisamos escrever o nome do método. Também não daremos new. Apenas declararemos os argumentos e o bloco a ser executado, separados por ->:

palavras.forEach((String s) -> {
    System.out.println(s);
});

É uma forma bem mais sucinta de escrever! Essa sintaxe funciona para qualquer interface que tenha apenas um método abstrato, e é por esse motivo que nem precisamos falar que estamos implementando o método accept, já que não há outra possibilidade. Podemos ir além e remover a declaração do tipo do parâmetro, que o compilador também infere:

palavras.forEach((s) -> {
    System.out.println(s);
});

Quando há apenas um parâmetro, nem mesmo os parenteses são necessários:

palavras.forEach(s -> {
    System.out.println(s);
});

Dá pra melhorar? Sim. podemos remover as chaves de declaração do bloco, assim como o ponto e vírgula, pois só existe uma única instrução:

palavras.forEach(s -> System.out.println(s));

Pronto. Em vez de usarmos classes anônimas, utilizamos o lambda para escrever códigos simples e sucintos nesses casos. Uma interface que possui apenas um método abstrato é agora conhecida como interface funcional e pode ser utilizada dessa forma.

Outro exemplo é o próprio Comparator que já vimos. Se utilizarmos a forma de classe anônima, teremos essa situação:

palavras.sort(new Comparator<String>() {
    public int compare(String s1, String s2) {
        if (s1.length() < s2.length())
            return -1;
        if (s1.length() > s2.length())
            return 1;
        return 0;
    }
});

Como aplicar a mesma lógica para transformar isso em um lambda? Basta removermos quase tudo da assinatura do método, assim como o new Comparator e adicionar o -> entre os parâmetros e o bloco. Além disso, podemos tirar o tipo dos parâmetros:

palavras.sort((s1, s2) -> {
    if (s1.length() < s2.length())
        return -1;
    if (s1.length() > s2.length())
        return 1;
    return 0;
});

Melhor? Parece que sim. Mas ainda não muito interessante. O lambda se encaixa melhor quando a expressão dentro do bloco é mais curta. Normalmente com apenas um statement. Conhecendo a API do Java, podemos ver que há um método que compara dois inteiros e retorna negativo/positivo/zero dependendo se o primeiro for menor/maior/igual ao segundo. É o Integer.compare. Com ele, reduzimos o lambda para o seguinte:

palavras.sort((s1, s2) -> {
    return Integer.compare(s1.length(), s2.length());
});

Dá para fazer melhor. Como há apenas um único statement, podemos remover as chaves. Além disso, o return pode ser eliminado que o compilador vai inferir que deve ser retornado o valor que o próprio compare devolver:

palavras.sort((s1, s2) -> Integer.compare(s1.length(), s2.length()));

Compare com a nossa primeira versão. Muito melhor! Claro que poderíamos ter utilizado o Integer.compare desde o capítulo anterior, mas a combinação com o lambda deixa tudo mais legível e simples.

Vale lembrar que não é porque digitamos menos linhas que o código é necessariamente mais simples. Às vezes, pouco código pode tornar difícil de entender uma ideia, um algoritmo. Não é o nosso caso.

Código mais sucinto com Method references - Código mais sucinto com Method references

Com os lambdas e métodos default, conseguimos escrever a ordenação das Strings de uma forma bem mais sucinta:

palavras.sort((s1, s2) -> Integer.compare(s1.length(), s2.length()));

Podemos ir além.

Métodos default em Comparator

Há vários métodos auxiliares no Java 8. Até em interfaces como o Comparator. E você pode ter um método default que é estático. Esse é o caso do Comparator.comparing, que é uma fábrica, uma factory, de Comparator. Passamos o lambda para dizer qual será o critério de comparação desse Comparator, repare:

palavras.sort(Comparator.comparing(s -> s.length()));

Veja a expressividade da linha, está escrito algo como "palavras ordene comparando s.length". Podemos quebrar em duas linhas para ver o que esse novo método faz exatamente:

Comparator<String> comparador = Comparator.comparing(s -> s.length());
palavras.sort(comparador);

Dizemos que Comparator.comparing recebe um lambda, mas essa é uma expressão do dia a dia. Na verdade, ela recebe uma instância de uma interface funcional. No caso é a interface Function que tem apenas um método, o apply. Para utilizarmos o Comparator.comparing, nem precisamos ficar decorando os tipos e assinatura do método dessas interfaces funcionais. Essa é uma vantagem dos lambdas. Você também vai acabar programando dessa forma. É claro que, com o tempo, você vai conhecer melhor as funções do pacote java.util.functions. Vamos quebrar o código mais um pouco. Não se esqueça de dar os devidos imports.

Function<String, Integer> funcao = s -> s.length();
Comparator<String> comparador = Comparator.comparing(funcao);
palavras.sort(comparador);

A interface Function vai nos ajudar a passar um objeto para o Comparator.comparing que diz qual será a informação que queremos usar como critério de comparação. Ela recebe dois tipos genéricos. No nosso caso, recebe uma String, que é o tipo que queremos comparar, e um Integer, que é o que queremos extrair dessa string para usar como critério. Poderia até mesmo criar uma classe anônima para implementar essa Function e seu método apply, sem utilizar nenhum lambda. O código ficaria grande e tedioso.

Quisemos quebrar em três linhas para que você pudesse enxergar o que ocorre por trás exatamente. Sem dúvida o palavras.sort(Comparator.comparing(s -> s.length())) é mais fácil de ler. Dá para melhorar ainda mais? Sim!

Method reference

É muito comum escrevermos lambdas curtos, que simplesmente invocam um único método. É o exemplo de s -> s.length(). Dada uma String, invoque e retorne o método length. Por esse motivo, há uma forma de escrever esse tipo de lambda de uma forma ainda mais reduzida. Em vez de fazer:

palavras.sort(Comparator.comparing(s -> s.length()));

Fazemos uma referência ao método (method reference):

palavras.sort(Comparator.comparing(String::length));

São equivalentes nesse caso! Sim, é estranho ver String::length e dizer que é equivalente a um lambda, pois não há nem a -> e nem os parênteses de invocação ao método. Por isso é chamado de method reference. Ela pode ficar ainda mais curta com o import static:

import static java.util.Comparator.*;
palavras.sort(comparing(String::length));

Vamos ver melhor a semelhança entre um lambda e seu method reference equivalente. Veja as duas declarações a seguir:

Function<String, Integer> funcao1 = s -> s.length();
Function<String, Integer> funcao2 = String::length;

Elas ambas geram a mesma função: dada um String, invoca o método length e devolve este Integer. As duas serão avaliadas/resolvidas (evaluated) para Functions equivalentes.

Quer um outro exemplo? Vejamos o nosso forEach, que recebe um Consumer:

palavras.forEach(s -> System.out.println(s));

Dada uma String, invoque o System.out.println passando-a como argumento. É possível usar method reference aqui também! Queremos invocar o println de System.out:

palavras.forEach(System.out::println);

Novamente pode parecer estranho. Não há os parênteses, não há a flechinha (->), nem os argumentos que o Consumer recebe. Fica tudo implícito. Dessa vez, o argumento recebido (isso é, cada palavra dentro da lista palavras), não será a variável onde o método será invocado. O Java 8 consegue perceber que tem um println que recebe objetos, e invocará esse método, passando a String da vez.

Quando usar lambda e quando usar method reference? Algumas vezes não é possível usar method references. Se você tiver, por exemplo, um lambda que dada uma String, pega os 5 primeiros caracteres, faríamos s -> s.substring(0, 5). Esse lambda não pode ser escrito como method reference! Pois não é uma simples invocação de métodos onde os parâmetros são os mesmos que os do lambda.

Sobre o curso Java 8: Tire proveito dos novos recursos da linguagem

O curso Java 8: Tire proveito dos novos recursos da linguagem possui 92 minutos de vídeos, em um total de 38 atividades. Gostou? Conheça nossos outros cursos de Java em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Java acessando integralmente esse e outros cursos, comece hoje!

  • 1155 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

Premium

  • 1155 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$75
à vista R$900
Matricule-se

Premium Plus

  • 1155 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$100
à vista R$1.200
Matricule-se

Max

  • 1155 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$120
à vista R$1.440
Matricule-se
Procurando planos para empresas?
Acesso por 1 ano
Estude 24h/dia onde e quando quiser
Novos cursos toda semana