
A interface em Java evoluiu bastante ao longo dos anos. Vejamos quais mudanças ocorreram no processo de seu desenvolvimento.
Interfaces originais
As interfaces no Java 1.0 eram bastante simples em comparação com o que são agora. Eles poderiam conter apenas dois tipos de elementos: constantes e métodos abstratos públicos.
Campos Constantes
As interfaces podem conter campos, assim como as classes regulares, mas com algumas diferenças:
- Os campos devem ser inicializados.
- Os campos são considerados finais estáticos públicos
- Os modificadores public, static e final não precisam ser especificados explicitamente (eles são "descartados" por padrão)
public interface MyInterface { int MY_CONSTANT = 9; }
Mesmo que isso não seja especificado explicitamente, o campo MY_CONSTANT é considerado uma constante final estática pública. Você pode adicionar esses modificadores, mas isso não é necessário.
Métodos abstratos
Os elementos mais importantes de uma interface são seus métodos. Os métodos de interface também diferem dos métodos de classe regulares:
- Métodos não têm corpo
- A implementação do método é fornecida por classes que implementam essa interface.
- Os métodos são considerados públicos e abstratos, mesmo que não sejam especificados explicitamente.
- Os métodos não podem ser finais, pois a combinação de modificadores abstrato e final não é permitida em Java
public interface MyInterface { int doSomething(); String doSomethingCompletelyDifferent(); }
Aninhamento
O Java 1.1 introduziu o conceito de classes que podem ser colocadas dentro de outras classes. Essas classes são de dois tipos: estático e não estático. As interfaces também podem conter outras interfaces e classes.
Mesmo que isso não seja especificado explicitamente, essas interfaces e classes são consideradas públicas e estáticas.
public interface MyInterface { class MyClass {
Enumerações e anotações
Mais dois tipos foram introduzidos no Java 5: Enumerações e Anotações. Eles também podem ser colocados dentro de interfaces.
public interface MyInterface { enum MyEnum { FOO, BAR; } @interface MyAnnotation {
Tipos genéricos
O Java 5 introduziu o conceito de genéricos, tipos genéricos. Resumindo: os genéricos permitem usar um tipo genérico em vez de especificar um tipo específico. Assim, você pode escrever um código que funcione com um número diferente de tipos sem sacrificar a segurança e sem fornecer uma implementação separada para cada tipo.
Nas interfaces iniciadas no Java 5, é possível definir um tipo genérico e usá-lo como o tipo do valor de retorno de um método ou como o tipo de um argumento para um método.
A interface Box funciona se você a usa para armazenar objetos como String, Inteiro, Lista, Sapato ou qualquer outro.
interface Box<T> { void insert(T item); } class ShoeBox implements Box<Shoe> { public void insert(Shoe item) {
Métodos estáticos
A partir do Java 8, você pode incluir métodos estáticos nas interfaces. Essa abordagem mudou a maneira como a interface funciona para nós. Agora, eles funcionam de maneira bem diferente da que era antes do Java 8. Inicialmente, todos os métodos nas interfaces eram abstratos. Isso significava que a interface fornecia apenas uma assinatura, mas não uma implementação. A implementação foi deixada para as classes que implementam sua interface.
Ao usar métodos estáticos em interfaces, você também precisa fornecer uma implementação do corpo do método. Para usar esse método em uma interface, basta usar a palavra-chave estática. Métodos estáticos são considerados públicos por padrão.
public interface MyInterface {
Herança de método estático
Diferentemente dos métodos estáticos regulares, os métodos estáticos nas interfaces não são herdados. Isso significa que, se você deseja chamar esse método, deve chamá-lo diretamente da interface, e não da classe que o implementa.
MyInterface.staticMethod();
Esse comportamento é muito útil para evitar vários problemas de herança. Imagine que você tem uma classe que implementa duas interfaces. Cada uma das interfaces possui um método estático com o mesmo nome e assinatura. Qual deles deve ser usado primeiro?
Por que é útil
Imagine que você tenha uma interface e um conjunto inteiro de métodos auxiliares que funcionam com classes que implementam essa interface.
Tradicionalmente, tem havido uma abordagem para o uso de uma classe complementar. Além da interface, uma classe de utilitário foi criada com um nome muito semelhante, contendo métodos estáticos pertencentes à interface.
Você pode encontrar exemplos de como usar essa abordagem diretamente no JDK: a interface java.util.Collection e a classe de utilitário java.util.Collections que a acompanha.
Com métodos estáticos nas interfaces, essa abordagem não é mais relevante, não é necessária e não é recomendada. Agora você pode ter tudo em um só lugar.
Métodos padrão
Os métodos padrão são semelhantes aos métodos estáticos, pois você também deve fornecer um corpo para eles. Para declarar um método padrão, basta usar a palavra-chave padrão.
public interface MyInterface { default int doSomething() { return 0; } }
Ao contrário dos métodos estáticos, os métodos são herdados por padrão pelas classes que implementam a interface. O que é importante, essas classes podem redefinir seu comportamento, se necessário.
Embora exista uma exceção. A interface não pode ter métodos padrão com a mesma assinatura que os métodos toString, equals e hashCode da classe Object. Dê uma olhada na resposta de Brian Goetz para entender a validade de uma solução:
Permita que os métodos padrão substituam os métodos de Object.Por que é útil
A idéia de implementar métodos diretamente na interface não parece totalmente correta. Então, por que essa funcionalidade foi introduzida pela primeira vez?
As interfaces têm um problema. Assim que você der sua API a outras pessoas, ela será "petrificada" para sempre (não poderá ser alterada sem problemas).
Por tradição, o Java leva a compatibilidade com versões anteriores muito a sério. Os métodos padrão fornecem uma maneira de estender as interfaces existentes com novos métodos. Mais importante ainda, os métodos padrão já fornecem uma implementação específica. Isso significa que as classes que implementam sua interface não precisam implementar novos métodos. Mas, se necessário, os métodos padrão podem ser substituídos a qualquer momento, se sua implementação deixar de ser adequada. Portanto, em resumo, você pode fornecer novas funcionalidades para as classes existentes que implementam sua interface, mantendo a compatibilidade.
Conflitos
Vamos imaginar que temos uma classe que implementa duas interfaces. Essas interfaces têm um método padrão com o mesmo nome e assinatura.
interface A { default int doSomething() { return 0; } } interface B { default int doSomething() { return 42; } } class MyClass implements A, B { }
Agora, o mesmo método padrão com a mesma assinatura é herdado de duas interfaces diferentes. Cada interface possui sua própria implementação desse método.
Então, como nossa classe sabe qual das duas implementações diferentes usar?
Ele não saberá. O código acima resultará em um erro de compilação. Se você precisar fazê-lo funcionar, substitua o método conflitante em sua classe.
interface A { default int doSomething() { return 0; } } interface B { default int doSomething() { return 42; } } class MyClass implements A, B {
Métodos particulares
Com o advento do Java 8 e a introdução de métodos padrão e métodos estáticos, as interfaces agora podem conter não apenas assinaturas de métodos, mas também sua implementação. Ao escrever essas implementações, recomenda-se que métodos complexos sejam divididos em métodos mais simples. Esse código é mais fácil de reutilizar, manter e entender.
Para esse fim, você usaria métodos privados, pois eles podem conter todos os detalhes de implementação que não devem ser visíveis e usados externamente.
Infelizmente no Java 8, uma interface não pode conter métodos privados. Isso significa que você pode usar:
- Técnicas corporais longas, complexas e difíceis de entender.
- Métodos auxiliares que fazem parte da interface. Isso viola o princípio do encapsulamento e polui a API pública das classes de interface e implementação.
Felizmente, a partir do
Java 9, você pode usar métodos privados em interfaces . Eles têm os seguintes recursos:
- métodos privados têm um corpo, eles não são abstratos
- eles podem ser estáticos ou não estáticos
- eles não são herdados por classes que implementam a interface e interfaces
- eles podem chamar outros métodos de interface
- métodos privados podem chamar outros métodos privados, abstratos, estáticos ou padrão
- métodos estáticos privados podem chamar apenas outros métodos estáticos e estáticos privados
public interface MyInterface { private static int staticMethod() { return 42; } private int nonStaticMethod() { return 0; } }
Ordem cronológica
A seguir, é apresentada uma lista cronológica de alterações por versão do Java:
Java 1.1
Classes aninhadas
Interfaces aninhadas
Java 5
Tipos genéricos
Transferências fechadas
Anotações aninhadas
Java 8
Métodos padrão
Métodos estáticos
Java 9
Métodos particulares