História da evolução das interfaces em Java

imagem

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 { //... } interface MyOtherInterface { //... } } 

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 { // This works static int foo() { return 0; } // This does not work, // static methods in interfaces need body static int bar(); } 

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 { // Without this the compilation fails @Override public int doSomething() { return 256; } } 

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:

  1. Técnicas corporais longas, complexas e difíceis de entender.
  2. 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

Source: https://habr.com/ru/post/pt482498/


All Articles