Design, Java, OO

Métodos default, colisão de interfaces e precedência de superclasses no Java 8

Métodos públicos e abstratos em Interfaces

Imagine que temos a interface Aquatico, que define um método move:

interface Aquatico {
	public abstract void move();
}

Se tivermos uma classe Navio que implementa essa interface mas não fornece uma implementação para o método move, teremos um erro de compilação:

class Navio implements Aquatico {
^
error: Navio is not abstract and does not override abstract method move() in Aquatico
}

Uma interface nos obriga a respeitar um contrato, ou seja, a assinatura (nome, parâmetros e retorno) de um ou mais métodos.

No Java, desde os primórdios, os métodos das interfaces são implicitamente public e abstract. Essa última palavra-chave indica que são métodos abstratos, ou seja, sem corpo definido e sem código, meros moldes a serem preenchidos.

Para o código da classe concreta Navio ser compilado com sucesso, temos que fornecer uma implementação para o método move. Por exemplo:

class Navio implements Aquatico {
	public void move() {
		System.out.println("Navega na água!");
	}
}

O código acima compila normalmente e podemos invocá-lo de maneira polimórfica, como a seguir:

public class Programa {
	public static void main(String[] args){
		Aquatico titanic = new Navio();
		titanic.move(); //imprime "Navega na água!"
	}
}

Métodos com mesma assinatura em Interfaces diferentes

Considere que temos uma interface Terrestre, conforme abaixo:

interface Terrestre {
	public abstract void move();
}

Poderíamos criar um classe Caminhao que implementa a interface Terrestre, de maneira semelhante ao que fizemos acima:

class Caminhao implements Terrestre {
	public void move() {
		System.out.println("Move na longa estrada da vida!");
	}
}

Para utilizar a classe Caminhao, faríamos de maneira parecida com a anterior:

public class Programa {
	public static void main(String[] args){
		Caminhao scania = new Caminhao();
		scania.move(); //imprime "Move na longa estrada da vida!"
	}
}

Mas o que acontece se tivermos uma classe que representa um Hovercraft, um veículo anfíbio, que pode se deslocar tanto na água como na terra?

Será que podemos implementar tanto a interface Aquatico como a interface Terrestre simultaneamente?

Claro que sim, já que a interface define apenas a assinatura (ou seja, nome, parâmetros e retorno) do método move e não uma implementação.

class Hovercraft implements Aquatico, Terrestre {
	public void move(){
		System.out.println("Move na água e na terra!");
	}
}

Não há problema algum, porque a implementação do método move na classe Hovercraft é a mesma tanto para Aquatico como para Terrestre.

Podemos usar a classe Hovercraft conforme a seguir:

public class Programa {
	public static void main(String[] args){
		Hovercraft srn4 = new Hovercraft();
		srn4.move(); //imprime "Move na terra e na água!"
	}
}

Métodos default em Interfaces no Java 8

Conforme vimos em um post anterior, no Java 8 agora é possível definir, além de contratos, implementações de métodos em interfaces.

Esse novo mecanismo é chamado de métodos default e, com ele, é possível evoluir interfaces antigas como a Iterable, que agora possui um método forEach, por exemplo.

No exemplo acima, poderíamos definir o comportamento de navegar na água diretamente na interface Aquatico, utilizando a palavra reservada default:

interface Aquatico {
	public default void move() {
		System.out.println("Navega na água!");
	}
}

Métodos default só podem ser definidos em interfaces e são implicitamente públicos e, é claro, não-abstratos porque possuem um corpo definido, com código.

A classe Navio já não precisará fornercer uma implementação para o método move:

class Navio implements Aquatico {
}	

A interface Terrestre também pode ter uma implementação do método move com um método default:

interface Terrestre {
	public default void move() {
		System.out.println("Move na longa estrada da vida!");
	}
}

A classe Caminhao também seria simplificada:

class Caminhao implements Terrestre {
}

O programa que utiliza as classes Navio e Caminhao, bem como suas respectivas interfaces, não precisa ser alterado:

public class Programa {
	public static void main(String[] args){
		Aquatico titanic = new Navio();
		titanic.move(); //imprime "Navega na água!"

		Caminhao scania = new Caminhao();
		scania.move(); //imprime "Move na longa estrada da vida!"
	}
}

Colisão de métodos default

Mas o que será que acontece com a nossa classe Hovercraft, que implementa tanto Aquatico como Terrestre?

class Hovercraft implements Aquatico, Terrestre {
^
error: class Hovercraft inherits unrelated defaults for move() from types Aquatico and Terrestre

Observe que, para a classe Hovercraft, o código não pode ser compilado. Como com um método default não apenas define uma assinatura mas também uma implementação, há ambiguidade na definição do método move. O compilador não pode definir qual das duas implementações deve ser escolhida e, por isso, nos avisa dessa colisão com um erro de compilação.

Para fazermos a classe Hovercraft compilar com sucesso, temos que definir uma implementação para o método move, evitando qualquer ambiguidade:

class Hovercraft implements Aquatico, Terrestre {
	public void move() {
		System.out.println("Move na terra e na água!");
	}
}

Uma alternativa é invocar um método default de uma interface a partir de uma classe que a implementa, utilizando o nome da interface seguido de super e do nome do método default. Por exemplo:

class Hovercraft implements Aquatico, Terrestre {
	public void move() {
		Aquatico.super.move();
		Terrestre.super.move();
		System.out.println("Move na terra e na água!");
	}
}

Se utilizarmos a classe Hovercraft acima em um programa, teremos algo como:

public class Programa {
	public static void main(String[] args){
		Hovercraft srn4 = new Hovercraft();
		srn4.move(); 
		// imprime 
		//	"Navega na água!"
		//	"Move na longa estrada da vida!"
		//	"Move na terra e na água!"
	}
}

Precedência de métodos da superclasse

Suponha que tenhamos uma interface Aereo, com um método default chamado move:

interface Aereo {
	public default void move() {
		System.out.println("Move no ar!");
	}
}

Também temos uma classe Aviao que implementa a interface Aereo, porém sobreescrevendo o método move:

class Aviao implements Aereo {
	public void move(){
		System.out.println("Voa...");
	}
}

Se criarmos um objeto da classe Aviao, qual método será chamado: o método sobreescrito na classe ou o método default da interface?

Será escolhido o método sobreescrito na classe Aviao, funcionando da mesma maneira que sobreescrita de métodos para classes:

public class Programa {
	public static void main(String[] args){
		Aviao boeing747 = new Aviao();
		boeing747.move(); //imprime "Voa..."
	}
}

Agora, se quisermos representar um Hidroavião, um tipo de avião que pode decolar e pousar na água, teremos uma classe semelhante a seguinte:

class HidroAviao extends Aviao implements Aquatico {
}

A classe HidroAviao é um Aviao que implementa a interface Aquatico.

Mas o que será que acontece ao invocarmos o método move de uma instância de HidroAviao? Será que é invocado a implementação da superclasse Aviao ou da interface Aquatico?

A implementação escolhida será a da superclasse:

public class Programa {
	public static void main(String[] args){
		HidroAviao curtissNC = new HidroAviao();
		curtissNC.move(); //imprime "Voa..."
	}
}

Uma implementação definida em uma superclasse sempre tem precedência sobre uma implementação definida em um método default de uma interface.

É importante lembrar que, em Java, uma classe só pode ter uma superclasse.

Resumindo

As regras do uso de métodos default são bem claras:

  1. Colisão de interfaces – se uma classe implementa duas interfaces que possuem métodos default com a mesma assinatura, o método deve ser sobreescrito
  2. Precedência de superclasses – se uma superclasse provê uma implementação de um método que tem a mesma assinatura de um método default de uma interface, a implementação da superclasse tem precedência
Anúncios
Design, Functional, Java, OO, Programming

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

Design, Functional, Java, OO, Programming

Menos Cerimônia e Mais Essência com Java 8

Recentemente, foi lançada a versão 8 do Java, com mudanças significativas na linguagem.

Mas qual é a razão pra tanta algazarra na Internet?

Com Java 8, é possível escrever código com menos cerimônia e mais essência.

Filtrando com Classes Anônimas no Java 7 ou anterior

No post anterior, vimos como melhorar nosso código utilizando o molde Strategy e classes anônimas.

Abstraímos, em uma classe chamada FiltroDeDesenhos, as operações de percorrer a lista, chamar a comparação e acumular os elementos uma nova lista. Já o contrato da lógica de comparação foi definido em uma interface chamada ComparacaoDeDesenhos.

O código resultante, que utiliza a modelagem acima para filtrar uma lista de desenhos, ficou assim:

FiltroDeDesenhos filtro = new FiltroDeDesenhos();

List<Desenho> antesDe1960 = filtro.filtra(desenhos, 
	new ComparacaoDeDesenhos() {
		public boolean valePara(Desenho desenho) {
			return desenho.getDecadaDeCriacao() < 1960;
		}
});
List<Desenho> comecamComS = filtro.filtra(desenhos, 
	new ComparacaoDeDesenhos() {
		public boolean valePara(Desenho desenho) {
			return desenho.getNome().startsWith("S");
		}
});

Focando no essencial com Lambdas do Java 8

As linhas destacadas no código acima são o realmente essencial: definem a lógica de filtragem. O resto todo faz parte da sintaxe assustadora de classes anônimas do Java.

A partir do Java 8, é possível simplificarmos nosso código com o uso de lambdas:

List<Desenho> antesDe1960 = filtro.filtra(desenhos,
	(Desenho desenho) -> {
		return desenho.getDecadaDeCriacao() < 1960;
	}
);
List<Desenho> comecamComS = filtro.filtra(desenhos,
	(Desenho desenho) -> {
		return desenho.getNome().startsWith("S");
	}
);

Lambdas são métodos anônimos que podem ser usados no lugar de classes anônimas, com uma sintaxe bem mais enxuta. Internamente, são implementadas com um mecanismo diferente das classes anônimas. Estão presentes na maioria das linguagens e são uma ótima nova adição à linguagem Java.

Só foi possível utilizar lambdas no lugar de classes anônimas no nosso caso porque nossa interface ComparacaoDeDesenhos é uma interface funcional: possui apenas um método abstrato.

Veja:

interface ComparacaoDeDesenhos {
	boolean valePara(Desenho desenho);
}

É possível marcar nossa interface com a anotação @FunctionalInterface, porém não é algo obrigatório. Essa anotação é usada pelo compilador para lançar um erro assim que um segundo método abstrato for declarado.

Podemos ainda omitir as chaves que definem o bloco e o return, já que o nosso lambda possui apenas uma expressão. Podemos também omitir a classe do parâmetro, por causa do mecanismo de inferência de tipos do Java 8, além dos parênteses:

List antesDe1960 = filtro.filtra(desenhos,
	desenho -> desenho.getDecadaDeCriacao() < 1960
);
List comecamComS = filtro.filtra(desenhos,
	desenho -> desenho.getNome().startsWith("S")
);

Evitando código desnecessário com Streams do Java 8

Podemos ir além e remover a nossa classe FiltroDeDesenhos e a interface ComparacaoDeDesenhos, utilizando um Stream, um novo recurso do Java 8:

Stream<Desenho> streamDeDesenhos = desenhos.stream();

Stream<Desenho> streamDosAntesDe1960 = 
    streamDeDesenhos
        .filter(desenho -> desenho.getDecadaDeCriacao() < 1960);

Stream<Desenho> streamDosQueComecamComS = 
    streamDeDesenhos
        .filter(desenho -> desenho.getNome().startsWith("S"));

Criamos um Stream a partir da nossa lista de desenhos invocando o método stream.

Um Stream é uma sequência de elementos que permitem a execução de várias operações. Uma delas é o filter, que recebe um lambda que tem a comparação e retorna um outro Stream que contém apenas os elementos em que a condição da comparação é válida.

O lambda (ou classe anônima) recebido pelo filter deve respeitar o contrato definido pela interface funcional Predicate. Essa interface recebe um objeto e retorna um booleano, de maneira parecida com a nossa interface ComparacaoDeDesenhos, mas mais genérica.

Mas o que podemos fazer com o Stream dos elementos filtrados? Podemos transformá-lo numa lista com .collect(Collectors.toList()) ou imprimir os elementos com:

streamDosAntesDe1960
	.forEach(System.out::println);
streamDosQueComecamComS
	.forEach(System.out::println);

Invocamos o método forEach passando um referência ao método println de System.out. Isso é equivalente ao lambda desenho -> System.out.println(desenho).

Juntando tudo

Pegando o código acima, explicitando a criação da lista e omitindo os streams intermediários, temos:

class ProgramaComStreams {
    public static void main(String[] args) {
        Desenho popeye = new Desenho("Popeye", 1920);
        Desenho picaPau = new Desenho("Pica-pau", 1940);
        Desenho flintstones = new Desenho("Flintstones", 1960);
        Desenho scoobyDoo = new Desenho("Scooby-Doo", 1970);
        Desenho simpsons = new Desenho("Simpsons", 1990);

        List<Desenho> desenhos = Arrays.asList(popeye, picaPau, 
                                        flintstones, scoobyDoo, simpsons);

        desenhos.stream()
	    .filter( desenho -> desenho.getDecadaDeCriacao() < 1960 )
            .forEach(System.out::println);
		
        System.out.println("---------------");
		
        desenhos.stream()
            .filter( desenho -> desenho.getNome().startsWith("S") )
            .forEach(System.out::println);
	}
}

Após executarmos o código acima, teríamos a seguinte saída:

Popeye (1920)
Pica-pau (1940)
---------------
Scooby-Doo (1970)
Simpsons (1990)

Comparando as implementações

Focando na filtragem dos desenhos anteriores a 1960, vamos comparar as implementações.

Primeiro, vamos lembrar da primeira versão, sem Strategy nem classes anônimas:

List<Desenho> antesDe1960 = new ArrayList<>();
for (Desenho desenho : desenhos) {
	if(desenho.getDecadaDeCriacao() < 1960){
		antesDe1960.add(desenho);
	}
}
for (Desenho desenho : antesDe1960) {
	System.out.println(desenho);
}

Agora, o código que cria uma implementação anônima da interface ComparacaoDeDesenhos:

List<Desenho> antesDe1960 = filtro.filtra(desenhos, 
	new ComparacaoDeDesenhos() {
		public boolean valePara(Desenho desenho) {
			return desenho.getDecadaDeCriacao() < 1960;
		}
});
for (Desenho desenho : antesDe1960) {
	System.out.println(desenho);
}

Então, usando lambas, temos uma sintaxe mais sucinta para definir a lógica de comparação:

List antesDe1960 = filtro.filtra(desenhos,
	desenho -> desenho.getDecadaDeCriacao() < 1960
);
for (Desenho desenho : antesDe1960) {
	System.out.println(desenho);
}

Finalmente, usando streams, deixamos de lado classes desnecessárias, além de imprimir os desenhos de maneira concisa:

desenhos.stream()
	.filter( desenho -> desenho.getDecadaDeCriacao() < 1960 )
	.forEach(System.out::println);

Compare os códigos e observe que, com lambdas e streams, a cerimônia do Java foi radicalmente diminuída. Podemos focar na essência da nossa tarefa: a lógica de filtragem.

Puxa… Achei confuso. E agora?

Realmente, utilizar lambdas, streams e as outras mudanças do Java 8 não é algo fácil. É preciso um bocado de estudo e de prática para entender e saber quando usar esses novos recursos.

Aqui, os conceitos foram vistos de maneira bem superficial. É importante estudá-los de maneira aprofundada. Há ainda muito mais coisas interessantes no Java 8: métodos default e estáticos em interfaces, optionals, streams paralelos, a nova API de datas, etc.

O livro Java 8 Prático, da Casa do Código é uma ótima referência. As novidades do Java 8 são estudadas com detalhes. Tive o prazer de participar da revisão técnica e recomendo! Os autores do livro também escreveram um post em que passeiam pelas funcionalidades mais importantes dessa nova versão do Java.

Não deixe de baixar a JDK 8.

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

Design, Java, OO, Programming

Evitando duplicação com Strategy e Classes Anônimas

Suponha que temos a seguinte classe Desenho:

class Desenho {

	private final String nome;
	private final int decadaDeCriacao;

	public Desenho(String nome, int decadaDeCriacao) {
		this.nome = nome;
		this.decadaDeCriacao = decadaDeCriacao;
	}

	public String getNome() {
		return nome;
	}

	public int getDecadaDeCriacao() {
		return decadaDeCriacao;
	}
	
	public String toString() {
		return nome + " (" + decadaDeCriacao + ")";
	}
}

Com a classe Desenho em mãos, vamos criar uma classe principal e alguns desenhos, colocando-os em uma lista:

class Programa {
	public static void main(String[] args) {
		Desenho popeye = new Desenho("Popeye", 1920);
		Desenho picaPau = new Desenho("Pica-pau", 1940);
		Desenho flintstones = new Desenho("Flintstones", 1960);
		Desenho scoobyDoo = new Desenho("Scooby-Doo", 1970);
		Desenho simpsons = new Desenho("Simpsons", 1990);
                
		List<Desenho> desenhos = 
                Arrays.asList(popeye, picaPau, 
                            flintstones, scoobyDoo, simpsons);
	}
}

Essa lista de desenhos poderia, ao invés de ser criada na mão, vir de um banco de dados através de uma DAO.

Dentro da main, vamos filtrar todos os desenhos que foram criados antes de 1960 e imprimi-los:

List<Desenho> antesDe1960 = new ArrayList<>();
for (Desenho desenho : desenhos) {
	if(desenho.getDecadaDeCriacao() < 1960){
		antesDe1960.add(desenho);
	}
}
for (Desenho desenho : antesDe1960) {
	System.out.println(desenho);
}

Deverá ser impresso:

Popeye (1920)
Pica-pau (1940)

E se quisermos filtrar todos os desenhos que começam com a letra “S”? Faríamos algo como:

List<Desenho> comecamComS = new ArrayList<>();
for (Desenho desenho : desenhos) {
	if(desenho.getNome().startsWith("S")){
		comecamComS.add(desenho);
	}
}
for (Desenho desenho : comecamComS) {
	System.out.println(desenho);
}

Teríamos a seguinte saída:

Scooby-Doo (1970)
Simpsons (1990)

Observem acima as linhas destacadas no código dos dois filtros. São as únicas linhas essenciais, com código que tem a ver com o que realmente queremos fazer: filtrar os desenhos.

O resto é código repetitivo, apenas dá suporte ao que queremos fazer. E esse código de suporte tem exatamente a mesma estrutura nos dois exemplos. Código duplicado, a raiz de todo o mal!

Evitando duplicação com Strategy e Classes Anônimas

Podemos evitar esse tipo de duplicação extraindo o código repetitivo para uma classe que filtra os desenhos:

class FiltroDeDesenhos {
	public List<Desenho> filtra(List<Desenho> desenhos){
		List<Desenho> filtrados = new ArrayList<>();
		for (Desenho desenho : desenhos) {
			if(???){
				filtrados.add(desenho);
			}
		}
		return filtrados;
	}
}

Mas a comparação vai variar de acordo com o que quisermos filtrar. E agora?

Podemos utilizar um molde chamado Strategy, de maneira a isolarmos as diferentes comparações cada uma em uma classe própria, respeitando um contrato comum.

Para definir um contrato em Java, utilizamos uma interface:

interface ComparacaoDeDesenhos {
	boolean valePara(Desenho desenho);
}

Devemos colocar a interface ComparacaoDeDesenhos como parâmetro do método filtra de FiltroDeDesenhos, usando-a para encapsular a lógica de filtragem dos desenhos de maneira polimórfica:

class FiltroDeDesenhos {
	public List<Desenho> filtra(List<Desenho> desenhos, 
				ComparacaoDeDesenhos comparacao){
		List<Desenho> filtrados = new ArrayList<>();
		for (Desenho desenho : desenhos) {
			if(comparacao.valePara(desenho)){
				filtrados.add(desenho);
			}
		}
		return filtrados;
	}
}

Para utilizarmos a nossa nova classe FiltroDeDesenhos para filtrar os desenhos criados antes de 1960, precisaríamos criar uma classe que atenda ao contrato definido pela interface ComparacaoDeDesenhos:

class ComparacaoDeDesenhosAntesDe1960 
				implements ComparacaoDeDesenhos {
	public boolean valePara(Desenho desenho) {
		return desenho.getDecadaDeCriacao() < 1960;
	}
} 

Então, poderíamos cria uma instância da nossa nova classe ComparacaoDeDesenhosAntesDe1960, e passá-la para o nosso FiltroDeDesenhos, depois imprimindo os desenhos filtrados:

FiltroDeDesenhos filtro = new FiltroDeDesenhos();

ComparacaoDeDesenhos comparacaoDeDesenhosAntesDe1960 = 
					new ComparacaoDeDesenhosAntesDe1960();

List<Desenho> antesDe1960 = filtro.filtra(desenhos, 
					comparacaoDeDesenhosAntesDe1960);
for (Desenho desenho : antesDe1960) {
       	System.out.println(desenho);
}

A saída seria a mesma de antes:

Popeye (1920)
Pica-pau (1940)

Para filtrar os desenhos que começam com “S”, teríamos que criar a classe ComparacaoDeDesenhosQueComecamComS.

Mas será que, pra todo o tipo de comparação, temos que criar uma classe diferente? Se fosse assim, o número de classes ia ser imenso.

Ao invés de criar a classe ComparacaoDeDesenhosAntesDe1960, poderíamos criar uma instância de uma classe sem nome, que atende à interface ComparacaoDeDesenhos, tudo de uma vez:

FiltroDeDesenhos filtro = new FiltroDeDesenhos();

List<Desenho> antesDe1960 = filtro.filtra(desenhos, 
	new ComparacaoDeDesenhos() {
		public boolean valePara(Desenho desenho) {
			return desenho.getDecadaDeCriacao() < 1960;
		}
});
for (Desenho desenho : antesDe1960) {
       	System.out.println(desenho);
}

Continuaríamos com a mesma saída:

Popeye (1920)
Pica-pau (1940)

Observe demos um new na interface ComparacaoDeDesenhos, seguido de uma implementação do método valePara, sem fornecer um nome para a classe.

Ao criarmos uma implementação de uma interface que não tem nome, estamos criando uma classe anônima.

O trio “});” é bem familiar para quem usa Javascript, já que aparece nas funções de callback usadas por muitas bibliotecas.

Para filtrar todos os desenhos que começam com “S”, faríamos:

List<Desenho> comecamComS = filtro.filtra(desenhos, 
	new ComparacaoDeDesenhos() {
		public boolean valePara(Desenho desenho) {
			return desenho.getNome().startsWith("S");
		}
});
for (Desenho desenho : comecamComS) {
	System.out.println(desenho);
}

Teríamos exatamente a mesma saída de antes:

Scooby-Doo (1970)
Simpsons (1990)

Comparando as implementações

Se deixarmos a impressão dos desenhos filtrados de fora, teríamos o seguinte código antes do uso de Strategy com classes anônimas:

List<Desenho> antesDe1960 = new ArrayList<>();
for (Desenho desenho : desenhos) {
       	if(desenho.getDecadaDeCriacao() < 1960){
       		antesDe1960.add(desenho);
       	}
}
List<Desenho> comecamComS = new ArrayList<>();
for (Desenho desenho : desenhos) {
       	if(desenho.getNome().startsWith("S")){
       		comecamComS.add(desenho);
       	}
}

Já o código com Strategy e classes anônimas, ficou assim:

List<Desenho> antesDe1960 = filtro.filtra(desenhos, 
	new ComparacaoDeDesenhos() {
		public boolean valePara(Desenho desenho) {
			return desenho.getDecadaDeCriacao() < 1960;
		}
});
List<Desenho> comecamComS = filtro.filtra(desenhos, 
	new ComparacaoDeDesenhos() {
		public boolean valePara(Desenho desenho) {
			return desenho.getNome().startsWith("S");
		}
});

O número de linhas ficou o mesmo, mas a modelagem da solução ficou melhor, evitando repetição do laço e do acúmulo dos desenhos filtrados em uma nova lista.

Uso de Strategy e Classes Anônimas em bibliotecas

É muito comum em bibliotecas do mundo Java utilizar o molde Strategy com classes anônimas.

Um exemplo bem comum é a interface Comparator da API de Collections do Java:

Collections.sort(desenhos, new Comparator<Desenho>() {
	public int compare(Desenho desenho1, Desenho desenho2) {
		return desenho1.getNome()
				.compareTo(desenho2.getNome());
	}
});
for (Desenho desenho : desenhos) {
	System.out.println(desenho);
}

O código acima, irá imprimir os desenhos ordenados por nome:

Flintstones (1960)
Pica-pau (1940)
Popeye (1920)
Scooby-Doo (1970)
Simpsons (1990)

Em frameworks como Spring, o uso de Strategy e classes anônimas é bastante comum. É usado, por exemplo, para gerenciar uma transação programaticamente:

transactionTemplate.execute(new TransactionCallback() {
	// código executado em uma transação
	public Object doInTransaction(TransactionStatus status) {
 		atualizarPedidos(compra.getPedidos());
		return atualizarTotalDaCompra(compra);
 	}
});

Concluindo

Através do uso de Strategy e classes anônimas, conseguimos criar uma solução que encapsula a estrutura, permitindo-nos focar na essência do que estamos fazendo e, dessa maneira, evitando duplicação.

Essa solução é bastante usada em bibliotecas e frameworks do mundo Java.

Porém, a sintaxe de classes anônimas em Java é assustadora.

No próximo post, veremos o que o Java 8 tem a oferecer para deixar nosso código mais sucinto.

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