Fundamentos das Interfaces
Uma interface é declarada através da palavra-chave interface, posicionando-se como um tipo de dados equivalente às classes no ecossistema Java. Sua estrutura interna evoluiu consideravelmente entre diferentes versões da linguagem:
- Até JDK 7: Restrita a constantes globais e métodos abstratos
- A partir de JDK 8: Incorporação de métodos estáticos e métodos default
As constantes em interfaces são implicitamente public static final, enquanto os métodos abstratos carregam implicitamente os modificadores public abstract. Não existe a possibilidade de instanciar uma interface diretamente, pois não possuem construtores.
Implementação e Herança Múltipla
A relação entre interface e classe concreta materializa-se através da palavra-chave implements. Uma classe concreta deve obrigatoriamenet sobrescrever todos os métodos abstratos herdados; caso contrário, permanece como classe abstrata.
Java permite que uma classe implemente múltiplas interfaces simultaneamente, contornando a limitação da herança simples entre classes:
class CalculadoraCientifica extends DispositivoEletronico implements OperacoesBasicas, OperacoesAvancadas, ConversaoUnidades {
// implementação dos métodos das três interfaces
}
Adicionalmente, interfaces podem estender outras interfaces, inclusive de forma múltipla:
interface OperacoesAvancadas extends Trigonometria, Logaritmos, Estatistica {
// novos métodos abstratos
}
Novidades do JDK 8
A versão 8 trouxe mudanças significativas, permitindo métodos com implementação direta:
public interface ProcessadorTexto {
// Método estático - pertence à interface, não às instâncias
public static String versaoSistema() {
return "ProcessadorTexto v2.1";
}
// Método default - disponível para todas as implementações
public default void exibirCabecalho() {
System.out.println("=== Documento Processado ===");
}
// Sintaxe alternativa sem 'public'
default void exibirRodape() {
System.out.println("=== Fim do Documento ===");
}
}
Resolução de Conflitos
A introdução de métodos default criou cenários que exigem regras específicas de resolução:
public class TesteProcessador {
public static void main(String[] args) {
ProcessadorPDF pdf = new ProcessadorPDF();
// Invocação direta via interface para métodos estáticos
String versao = ProcessadorTexto.versaoSistema();
// Chamada através da instância para métodos default
pdf.exibirCabecalho();
}
}
Quando uma classe herda método de mesma assinatura tanto de superclasse quanto de interface, prevalece a implementação da superclasse (princípio da prioridade de classe). Para interfaces em conflito entre si, a classe implementadora deve obrigatoriamente sobrescrever o método:
class ProcessadorPDF extends FerramentaBase implements ProcessadorTexto, FormatadorLayout {
@Override
public void exibirCabecalho() {
System.out.println("Cabeçalho personalizado PDF");
}
// Acesso explícito a implementações específicas
public void configurarVisualizacao() {
exibirCabecalho(); // versão local
super.aplicarEstilo(); // da superclasse
ProcessadorTexto.super.exibirRodape(); // da interface A
FormatadorLayout.super.exibirRodape(); // da interface B
}
}
Interfaces de Marcação
Interfaces vazias, conhecidas como marker interfaces, desempenham papel importante no sistema de tipos Java. Elas não declaram métodos ou atributos, funcionando exclusivamente como indicadores de capacidade:
package java.io;
public interface Serializavel {
// interface intencionalmente vazia
}
Sua utilidade divide-se em duas categorias principais:
- Agrupamento hierárquico: Servir como raiz comum para famílias de interfaces relacionadas, como
java.util.EventListenerpara tartamento de eventos - Identificação de tipo: Permitir verificações em tempo de execução através do operador
instanceof, habilitando comportamentos específicos em APIs como serialização e clonagem
A verificação de tipos através de interfaces de marcação ocorre frequentemente em operações de entrada/saída, onde a presença da interface Serializable determina se um objeto pode ser convertido em fluxo de bytes.
Considerações sobre Constantes
Quando constantes de mesmo nome existem em interfaces e classes ancestrais, a ambiguidade é resolvida explicitamente através da notação qualificada: NomeInterface.CONSTANTE ou super.CONSTANTE, evitando comportamentos indefinidos na compilação.