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

Um comentário sobre “Métodos default, colisão de interfaces e precedência de superclasses no Java 8

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s