Artigo original: Java Interfaces Explained with Examples

A interface em Java se parece um pouco com as classes, mas tem uma diferença importante: uma interface pode ter somente assinaturas de métodos, campos e métodos padrão. Desde o Java 8, você também pode criar métodos padrão (documentação em inglês). No bloco seguinte, você verá um exemplo de interface:

public interface Veiculo {
    public String placa = "";
    public float velMax
    public void iniciar();
    public void parar();
    default void buzinar(){
      System.out.println("Buzinando");
   }
}

A interface acima contém dois campos, dois métodos e um método padrão. Sozinha, ela não tem uma finalidade específica, mas a interface geralmente é usada em conjunto com as Classes. Como? É simples. você precisa garantir que alguma classe a implemente.

public class Carro implements Veiculo {
    public void iniciar() {
        System.out.println("ligando o motor...");
    }
    public void parar() {
        System.out.println("parando o motor...");
    }
}

Temos, porém, uma regra geral: a Classe deve implementar todos os métodos da Interface. Os métodos devem ter exatamente a mesma assinatura (nome, parâmetros e exceções) descrita na interface. No entanto, a classe não precisa declarar os campos, apenas os métodos.

Instâncias de uma interface

Ao criar uma Classe do Java, que implementa uma Interface, a instância do objeto pode ser referenciada como uma instância da interface. Esse conceito é semelhante ao de instanciação por herança.

// seguindo o exemplo anterior

Veiculo tesla = new Carro();

tesla.iniciar(); // ligando o motor...

Uma interface não pode conter um método construtor. Por isso, você não pode criar uma instância da própria interface. É preciso criar uma instância de alguma classe que implemente a interface para fazer referência a ela.

Pense nas interfaces como se elas fossem um formulário de contrato em branco ou um modelo.

O que é possível fazer com esse recurso? Polimorfismo! Você pode usar apenas interfaces para fazer referência a instâncias de objeto!

class Caminhao implements Veiculo {
    public void iniciar() {
        System.out.println("ligando o motor do caminhão...");
    }
    public void parar() {
        System.out.println("parando o motor do caminhão...");
    }
}

class Iniciador {
    // método estático, pode ser chamado sem instanciar a classe
    public static void iniciarMotor(Veiculo veiculo) {
        veiculo.iniciar();
    }
}

Veiculo tesla = new Carro();
Veiculo tata = new Caminhao();

Iniciador.iniciarMotor(tesla); // ligando o motor...
Iniciador.iniciarMotor(tata); // ligando o motor do caminhão...

Existe herança múltipla?

Sim, você pode implementar várias interfaces em uma única classe. Embora na herança dentro das Classes você esteja restrito a herdar apenas uma classe, com as interfaces, você pode herdar várias. Não se esqueça, porém, de implementar todos os métodos de todas as interfaces para não gerar um erro de compilação!

public interface GPS {
    public void obterCoordenadas();
}

public interface Radio {
    public void ligarRadio();
    public void pararRadio();
}

public class Smartphone implements GPS,Radio {
    public void obterCoordenadas() {
        // retorna coordenadas
    }
    public void ligarRadio() {
      // liga o rádio
    }
    public void pararRadio() {
        // desliga o rádio
    }
}

Alguns recursos das interfaces

  • É possível colocar variáveis em uma interface, embora não seja algo sensato, pois as Classes não deveriam ficar restritas a ter a mesma variável. Em resumo, evite inserir variáveis nas interfaces!
  • Todas as variáveis em uma interface são públicas, mesmo que você não inclua a palavra-chave public.
  • Uma interface não pode especificar a implementação de um método. Essa é uma tarefa das classes. Há, contudo, uma exceção recente (veja abaixo).
  • Se uma classe implementa diversas interfaces, há a possibilidade de uma sobreposição de métodos. Como o Java não permite que métodos diferentes tenham exatamente a mesma assinatura, isso pode causar problemas. Esta pergunta no Stack Overflow (texto em inglês) fornece mais informações.

Métodos padrão das interfaces

Antes do Java 8, não havia como orientar uma interface para que tivesse uma implementação específica de um método. Isso gerava muita confusão e problemas no código caso uma interface fosse mudada de repente.

Imagine que você escreveu uma biblioteca de código aberto contendo uma interface. Vamos supor que um de seus clientes, por exemplo, desenvolvedores de todas as partes do mundo, a utilizem muito e que estejam felizes com ela. De repente, você precisa atualizar a biblioteca adicionando a ela uma nova definição de método à interface para dar suporte a um novo recurso. Você acaba causando um problema para todas as builds que possuem aquela interface, já que todas as classes que a implementam precisam mudar. Que catástrofe!

Felizmente, o Java 8 fornece os métodos default (padrão) para as interfaces. Um método default pode conter sua própria implementação diretamente dentro da interface! Assim, se uma classe não implementar um método padrão, o compilador usará a  implementação mencionada na interface. Isso é bom, certo? Desse modo, em sua biblioteca, você pode adicionar quantos métodos padrão quiser nas interfaces sem medo de quebrar coisa alguma!

public interface GPS {
    public void obterCoordenadas();
    default public void obterCoordenadasAproximadas() {
        // implementação para retornar coordenadas de fontes aproximadas
        // como o wi-fi e dispositivos móveis
        System.out.println("Obtendo coordenadas aproximadas...");
    }
}

public interface Radio {
    public void ligarRadio();
    public void pararRadio();
}

public class Smartphone implements GPS,Radio {
    public void obterCoordenadas() {
        // retorna coordenadas
    }
    public void ligarRadio() {
      // liga o rádio
    }
    public void pararRadio() {
        // desliga o rádio
    }

    // sem implementação de obterCoordenadasAproximadas()
}

Smartphone motoG = new Smartphone();
motog.obterCoordenadasAproximadas(); // Obtendo coordenadas aproximadas...

O que acontece, no entanto, se duas interfaces têm a mesma assinatura de métodos?

Ótima pergunta. Nesse caso, se você não fornecer a implementação na classe, o pobre compilador se confundirá todo e simplesmente causará um erro! É preciso fornecer a implementação do método padrão dentro da classe também. Existe, além disso, uma maneira bem legal de usar super para chamar a implementação que você deseja:

public interface Radio {
    // public void ligarRadio();
    // public void pararRadio();

    default public void proxima() {
        System.out.println("Próxima de Radio");
    }
}

public interface ReprodutorMusical {
    // public void iniciar();
    // public void pausar();
    // public void parar();

    default public void proxima() {
        System.out.println("Próxima de ReprodutorMusical");
    }
}

public class Smartphone implements Radio, MusicPlayer {
    public void proxima() {
        // Imaginando que você queira chamar proxima de ReprodutorMusical
        ReprodutorMusical.super.proxima();
    }
}

Smartphone motoG = new Smartphone();
motoG.proxima(); // Próxima de ReprodutorMusical

Métodos estáticos nas interfaces

Outra coisa que surgiu com o Java 8 foi a capacidade de adicionar métodos estáticos às interfaces. Os métodos estáticos nas interfaces são quase idênticos aos métodos estáticos nas classes concretas. A única e grande diferença está no fato de que os métodos static não são herdados nas classes que implementam a interface. Isso quer dizer que é a interface quem é referenciada ao chamar o método estático, não a classe que implementa a interface.

interface ReprodutorMusical {
  public static void comercial(String patrocinador) {
    System.out.println("Agora, aqui vai uma mensagem trazida até você por " + patrocinador);
  }
  
  public void reproduzir();
}


class Smartphone implements ReprodutorMusical {
	public void reproduzir() {
		System.out.println("Reproduzindo no smartphone");
	}
}

class Main {
  public static void main(String[] args) {
    Smartphone motoG = new Smartphone();
    ReprodutorMusical.comercial("Motorola"); // Chamada a partir da interface, não da classe que a implementa
    // motoG.comercial("Motorola"); // Isso causaria um erro de compilação
  }
}

Herdando uma interface

Também é possível, em Java, que uma interface herde outra interface usando a palavra-chave extends:

public interface Reprodutor {
    public void iniciar();
    public void pausar();
    public void parar();
}

public interface ReprodutorMusical extends Reprodutor {
    default public void proxima() {
        System.out.println("Próxima de ReprodutorMusical");
    }
}

Isso significa que a classe que implementa a interface ReprodutorMusical precisa implementar todos os métodos de ReprodutorMusical, assim como de Reprodutor:

public class SmartPhone implements ReprodutorMusical {
    public void iniciar() {
        System.out.println("iniciar");
    }
    public void parar() {
        System.out.println("parar");
    }
    public void pausar() {
        System.out.println("pausar");
    }
}

Agora, você já tem um bom entendimento das interfaces em Java! Siga aprendendo, descubra mais sobre as classes abstratas e veja como o Java oferece a você mais uma maneira de definir contratos.