Iteração interna com Java 8 e métodos default

Iterando externamente

Vamos dizer que temos uma lista com nomes de personagens da Disney:

List<String> personagens = Arrays.asList("Pato Donald",
                            "Mickey", "Pateta", "Pluto");

Temos algumas opções para percorrer essa lista e imprimir o conteúdo. A mais intuitiva para iniciantes, é um for:

  for(int i = 0; i < personagens.size(); i++){
    String personagem = personagens.get(i);
    System.out.println(personagem);
  }

Uma opção mais interessante, mas que requer um pouco mais de conhecimento das bibliotecas do Java, é utilizar um Iterator:

  for(Iterator<String> it = personagens.iterator(); it.hasNext();){
    String personagem = it.next();
    System.out.println(personagem);
  }

Ambas as opções, se desconsiderarmos o uso de tipos genéricos, já funcionavam até o Java 4. Porém, do Java 5 em diante, percorrer uma lista ficou mais conciso através do for-each:

  for(String personagem : personagens){
    System.out.println(personagem);
  }

Mesmo com um for-each do Java 5, percorrer uma lista é algo tedioso e repetitivo. Quantas vezes um desenvolvedor Java experiente já escreveu um for desse tipo durante a carreira?

Com esse tipo de código, o desenvolvedor precisa se preocupar em “como” percorrer uma lista ao invés de focar apenas no mais importante: “o que” fazer com os elementos.

Essa maneira clássica, que deixa o “como” a cargo do desenvolvedor, pode ser chamada de iteração externa.

Iterando internamente

Com o Java 8, é possível focar apenas no “o que” fazer com os elementos da lista percorrida, utilizando o método forEach:

personagens.forEach(personagem -> System.out.println(personagem));

Observe que o método forEach pertence à própria lista e recebe um lambda.

Na verdade, pode ser passado qualquer lambda ou classe anônima que atenda à interface funcional Consumer, que recebe um argumento e não retorna resultado.

Poderíamos ter abreviado mais ainda o código utilizando uma referência ao método System.out.println:

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

Essa maneira nova, que permite ao desenvolvedor focar no “o que” fazer com os elementos, pode ser chamada de iteração interna.

Inserindo implementações em interfaces antigas com métodos default

O método forEach está definido em Iterable e, por consequência, em todos os List, Set e Queue da API de Coleções do Java. Porém, esse método não é abstrato.

O que iria acontecer se colocassem mais um método abstrato na interface Iterable? Código de várias bibliotecas e frameworks por aí, que fornececem suas próprias implementações para essas classes, iriam deixar de funcionar. E o Java tem como um de seus princípios mais fortes a retrocompatibilidade e evolução suave (para muitos, suave até demais).

Para evitar qualquer quebra inaceitável, foi criada uma implementação concreta na interface Iterable para o método forEach. Como foi possível inserir uma implementação em uma interface? Antes do Java 8, as interfaces só podiam ter métodos públicos e abstratos, afinal de contas…

Foi criado um novo mecanismo: os métodos default. Para definir um método default, criamos um método com a palavra-chave default e, em seguida, fornecemos uma implementação para o método.

Veja o que aconteceu com a interface Iterable:

package java.lang;

//imports ...

public interface Iterable<T> {

  Iterator<T> iterator(); //método implicitamente 
                          //abstrato e público 

  default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
      for (T t : this) {
        action.accept(t);
      }
  }

  //...
}

Métodos default permitem evoluir uma API antiga de maneira não disruptiva, sem romper com o passado. A implementação definida em uma método default é “copiada” para todas as classes que implementam essa interface. Em um próximo post, veremos com mais detalhes como funciona esse mecanismo.

Métodos default acabaram com a prática comum de ter uma interface acompanhada de uma superclasse abstrata que fornece implementações para a maioria dos métodos e deixando apenas detalhes para as subclasses concretas.

Um exemplo disso é o par List e AbstractList. Agora, se fossemos reprojetar a API de Coleções do Java, poderíamos colocar boa parte do código de AbstractList como métodos default da interface List.

O código desse post pode ser encontrado em: https://gist.github.com/alexandreaquiles/9753849

Anúncios