java - Existe mais em uma interface do que ter os métodos corretos




oop language-features (15)

Interfaces onde uma fetature adicionada ao java para permitir herança múltipla. Os desenvolvedores de Java embora / perceberam que ter herança múltipla era um recurso "perigoso", por isso surgiu a ideia de uma interface.

herança múltipla é perigosa porque você pode ter uma classe como a seguinte:


class Box{
    public int getSize(){
       return 0;
    }
    public int getArea(){
       return 1;
    }

}

class Triangle{
    public int getSize(){
       return 1;
    }
    public int getArea(){
       return 0;
    }

}

class FunckyFigure extends Box, Triable{
   // we do not implement the methods we will used the inherited ones
}

Qual seria o método que deveria ser chamado quando usamos


   FunckyFigure.GetArea(); 

Todos os problemas são resolvidos com interfaces, porque você sabe que pode estender as interfaces e que eles não terão métodos de classe ... claro que o compilador é legal e diz se você não implementou um método, mas eu gosto de pensar que é um efeito colateral de uma ideia mais interessante.

Então, digamos que eu tenha essa interface:

public interface IBox
{
   public void setSize(int size);
   public int getSize();
   public int getArea();
  //...and so on
}

E eu tenho uma classe que implementa isso:

public class Rectangle implements IBox
{
   private int size;
   //Methods here
}

Se eu quisesse usar a interface IBox, na verdade não posso criar uma instância dela, da seguinte forma:

public static void main(String args[])
{
    Ibox myBox=new Ibox();
}

certo? Então eu realmente tenho que fazer isso:

public static void main(String args[])
{
    Rectangle myBox=new Rectangle();
}

Se isso for verdade, então o único propósito das interfaces é garantir que a classe que implementa uma interface tenha os métodos corretos como descritos por uma interface? Ou existe algum outro uso de interfaces?


PORQUE INTERFACE ??????

Começa com um cachorro. Em particular, um pug .

O pug tem vários comportamentos:

public class Pug { 
private String name;
public Pug(String n) { name = n; } 
public String getName() { return name; }  
public String bark() { return  "Arf!"; } 
public boolean hasCurlyTail() { return true; } }

E você tem um Labrador, que também tem um conjunto de comportamentos.

public class Lab { 
private String name; 
public Lab(String n) { name = n; } 
public String getName() { return name; } 
public String bark() { return "Woof!"; } 
public boolean hasCurlyTail() { return false; } }

Nós podemos fazer alguns pugs e laboratórios:

Pug pug = new Pug("Spot"); 
Lab lab = new Lab("Fido");

E podemos invocar seus comportamentos:

pug.bark() -> "Arf!" 
lab.bark() -> "Woof!" 
pug.hasCurlyTail() -> true 
lab.hasCurlyTail() -> false 
pug.getName() -> "Spot"

Vamos dizer que eu corro um canil e eu preciso acompanhar todos os cães que estou abrigando. Eu preciso armazenar meus pugs e labradores em matrizes separadas :

public class Kennel { 
Pug[] pugs = new Pug[10]; 
Lab[] labs = new Lab[10];  
public void addPug(Pug p) { ... } 
public void addLab(Lab l) { ... } 
public void printDogs() { // Display names of all the dogs } }

Mas isso claramente não é o ideal. Se eu quiser abrigar alguns poodles também, eu tenho que mudar minha definição de canil para adicionar uma variedade de Poodles. Na verdade, preciso de um array separado para cada tipo de cachorro.

Perspicácia: tanto os pugs como os labradores (e poodles) são tipos de cães e têm o mesmo conjunto de comportamentos. Ou seja, podemos dizer (para os propósitos deste exemplo) que todos os cães podem latir, ter um nome e podem ou não ter uma cauda encaracolada. Podemos usar uma interface para definir o que todos os cães podem fazer, mas deixar para os tipos específicos de cães para implementar esses comportamentos específicos. A interface diz "aqui estão as coisas que todos os cães podem fazer", mas não diz como cada comportamento é feito.

public interface Dog 
{
public String bark(); 
public String getName(); 
public boolean hasCurlyTail(); }

Então eu altero ligeiramente as classes Pug e Lab para implementar os comportamentos Dog. Podemos dizer que um Pug é um cachorro e um laboratório é um cachorro.

public class Pug implements Dog {
// the rest is the same as before } 

public class Lab implements Dog { 
// the rest is the same as before 
}

Ainda posso instanciar Pugs e Labs como anteriormente, mas agora também tenho uma nova maneira de fazer isso:

Dog d1 = new Pug("Spot"); 
Dog d2 = new Lab("Fido");

Isso diz que d1 não é apenas um cachorro, é especificamente um pug. E d2 também é um cachorro, especificamente um laboratório. Podemos invocar os comportamentos e eles funcionam como antes:

d1.bark() -> "Arf!" 
d2.bark() -> "Woof!" 
d1.hasCurlyTail() -> true 
d2.hasCurlyTail() -> false 
d1.getName() -> "Spot"

Aqui é onde todo o trabalho extra compensa. A turma do Kennel se torna muito mais simples. Eu preciso apenas de um array e de um método addDog. Ambos irão trabalhar com qualquer objeto que seja um cachorro; isto é, objetos que implementam a interface Dog.

public class Kennel {
Dog[] dogs = new Dog[20]; 
public void addDog(Dog d) { ... } 
public void printDogs() {
// Display names of all the dogs } }

Veja como usá-lo:

Kennel k = new Kennel(); 
Dog d1 = new Pug("Spot"); 
Dog d2 = new Lab("Fido"); 
k.addDog(d1); 
k.addDog(d2); 
k.printDogs();

A última declaração seria exibida: Spot Fido

Uma interface permite que você especifique um conjunto de comportamentos que todas as classes que implementam a interface compartilharão em comum. Consequentemente, podemos definir variáveis ​​e coleções (como arrays) que não precisam saber de antemão que tipo de objeto específico elas conterão, apenas que elas conterão objetos que implementam a interface.


O único propósito das interfaces é garantir que a classe que implementa uma interface tenha os métodos corretos, conforme descrito por uma interface? Ou existe algum outro uso de interfaces?

Estou atualizando a resposta com novos recursos da interface, que foram introduzidos com a versão do java 8 .

Da página de documentação do oracle no resumo da interface :

Uma declaração de interface pode conter

  1. assinaturas de método
  2. métodos padrão
  3. métodos estáticos
  4. definições constantes.

Os únicos métodos que possuem implementações são os métodos padrão e estático.

Usos da interface :

  1. Para definir um contrato
  2. Para vincular classes não relacionadas com tem uma capacidade (por exemplo, classes implementando interface Serializable pode ou não ter qualquer relação entre eles, exceto a implementação dessa interface
  3. Para fornecer implementação intercambiável, por exemplo, padrão de estratégia
  4. Os métodos padrão permitem adicionar novas funcionalidades às interfaces de suas bibliotecas e garantir compatibilidade binária com código escrito para versões mais antigas dessas interfaces
  5. Organize métodos auxiliares em suas bibliotecas com métodos estáticos (é possível manter métodos estáticos específicos para uma interface na mesma interface em vez de em uma classe separada)

Algumas questões SE relacionadas com relação à diferença entre classe abstrata e interface e casos de uso com exemplos de trabalho:

Qual é a diferença entre uma interface e uma classe abstrata?

Como devo ter explicado a diferença entre uma interface e uma classe abstrata?

Dê uma olhada na página de documentation para entender os novos recursos adicionados no java 8: métodos padrão e métodos estáticos .


Normalmente as interfaces definem a interface que você deve usar (como o nome diz ;-)). Amostra


public void foo(List l) {
   ... do something
}

Agora sua função foo aceita ArrayList s, LinkedList s, ... não apenas um tipo.

A coisa mais importante em Java é que você pode implementar várias interfaces, mas você só pode estender uma classe! Amostra:


class Test extends Foo implements Comparable, Serializable, Formattable {
...
}
é possível, mas

class Test extends Foo, Bar, Buz {
...
}
não é!

Seu código acima também pode ser: IBox myBox = new Rectangle(); . O importante agora é que myBox SOMENTE contém os métodos / campos do IBox e não os (possivelmente existentes) outros métodos do Rectangle .


Esta é a razão pela qual Factory Patterns e outros padrões de criação são tão populares em Java. Você está certo de que, sem eles, o Java não fornece um mecanismo pronto para uso para fácil abstração da instanciação. Ainda assim, você terá a abstração em todos os lugares onde você não cria um objeto em seu método, que deve ser a maior parte do seu código.

Como um aparte, geralmente encorajo as pessoas a não seguirem o mecanismo "IRealname" para nomear interfaces. Essa é uma coisa do Windows / COM que coloca um pé no túmulo da notação húngara e realmente não é necessário (o Java já é fortemente tipado, e todo o objetivo de ter interfaces é tê-los como indistinguíveis dos tipos de classe).


Um ótimo exemplo de como as interfaces são usadas está na estrutura Coleções. Se você escrever uma função que leve uma List , não importa se o usuário passar um Vector ou uma ArrayList ou uma HashList ou qualquer HashList coisa. E você pode passar essa List para qualquer função que requeira também uma interface Collection ou Iterable .

Isso faz com que funções como Collections.sort(List list) possíveis, independentemente de como a List é implementada.


Um dos muitos usos que tenho lido é onde é difícil sem usar interfaces de herança múltipla em Java:

class Animal
{
void walk() { } 
....
.... //other methods and finally
void chew() { } //concentrate on this
} 

Agora, imagine um caso em que:

class Reptile extends Animal 
{ 
//reptile specific code here
} //not a problem here

mas,

class Bird extends Animal
{
...... //other Bird specific code
} //now Birds cannot chew so this would a problem in the sense Bird classes can also call chew() method which is unwanted

Melhor design seria:

class Animal
{
void walk() { } 
....
.... //other methods 
} 

Animal não tem o método chew () e, em vez disso, é colocado em uma interface como:

interface Chewable {
void chew();
}

e ter a classe Reptile implementando isso e não os Birds (já que os Birds não podem mastigar):

class Reptile extends Animal implements Chewable { } 

e incase de pássaros simplesmente:

class Bird extends Animal { }

você poderia fazer

Ibox myBox = new Rectangle();

Dessa forma você está usando este objeto como Ibox e você não se importa que seja realmente Rectangle .


Aqui está o meu entendimento da vantagem da interface. Corrija-me se eu estiver enganado. Imagine que estamos desenvolvendo o sistema operacional e outra equipe está desenvolvendo os drivers para alguns dispositivos. Por isso, desenvolvemos uma interface StorageDevice. Temos duas implementações (FDD e HDD) fornecidas por outros desenvolvedores.

Então nós temos uma classe OperatingSystem que pode chamar métodos de interface como saveData apenas passando uma instância de classe implementada a interface StorageDevice.

A vantagem aqui é que não nos importamos com a implementação da interface. A outra equipe fará o trabalho implementando a interface StorageDevice.

package mypack;

interface StorageDevice {
    void saveData (String data);
}


class FDD implements StorageDevice {
    public void saveData (String data) {
        System.out.println("Save to floppy drive! Data: "+data);
    }
}

class HDD implements StorageDevice {
    public void saveData (String data) {
        System.out.println("Save to hard disk drive! Data: "+data);
    }
}

class OperatingSystem {
    public String name;
    StorageDevice[] devices;
    public OperatingSystem(String name, StorageDevice[] devices) {

        this.name = name;
        this.devices = devices.clone();

        System.out.println("Running OS " + this.name);
        System.out.println("List with storage devices available:");
        for (StorageDevice s: devices) {
            System.out.println(s);
        }

    }

    public void saveSomeDataToStorageDevice (StorageDevice storage, String data) {
        storage.saveData(data);
    }
}

public class Main {

    public static void main(String[] args) {

        StorageDevice fdd0 = new FDD();
        StorageDevice hdd0 = new HDD();     
        StorageDevice[] devs = {fdd0, hdd0};        
        OperatingSystem os = new OperatingSystem("Linux", devs);
        os.saveSomeDataToStorageDevice(fdd0, "blah, blah, blah...");    
    }
}

O que torna as interfaces úteis não é o fato de que "você pode mudar de idéia e usar uma implementação diferente posteriormente e só precisa alterar o único local onde o objeto é criado". Isso não é problema.

O ponto real já está no nome: eles definem uma interface que qualquer um pode implementar para usar todo o código que opera nessa interface. O melhor exemplo é java.util.Collections que fornece todos os tipos de métodos úteis que operam exclusivamente em interfaces, como sort() ou reverse() para List . O ponto aqui é que esse código agora pode ser usado para classificar ou reverter qualquer classe que implemente as interfaces List - não apenas ArrayList e LinkedList , mas também classes que você mesmo escreve, que podem ser implementadas de uma forma que as pessoas escrevam java.util.Collections nunca imaginou.

Da mesma forma, você pode escrever código que opera em interfaces conhecidas, ou interfaces que você define, e outras pessoas podem usar seu código sem ter que pedir para você dar suporte a suas classes.

Outro uso comum de interfaces é para retornos de chamada. Por exemplo, java.swing.table.TableCellRenderer , que permite influenciar como uma tabela Swing exibe os dados em uma determinada coluna. Você implementa essa interface, passa uma instância para o JTable e, em algum momento durante a renderização da tabela, seu código será chamado para fazer suas coisas.


Se você tiver o CardboardBox e o HtmlBox (ambos implementam o IBox), você pode passar ambos para qualquer método que aceite um IBox. Embora sejam muito diferentes e não sejam completamente intercambiáveis, os métodos que não se importam com "abrir" ou "redimensionar" ainda podem usar suas classes (talvez porque se importem com quantos pixels são necessários para exibir algo em uma tela).


Eu acho que você entende tudo o que as Interfaces fazem, mas você ainda não está imaginando as situações nas quais uma Interface é útil.

Se você estiver instanciando, usando e liberando um objeto dentro de um escopo restrito (por exemplo, dentro de uma chamada de método), uma interface realmente não adiciona nada. Como você observou, a classe concreta é conhecida.

Onde Interfaces são úteis é quando um objeto precisa ser criado em um lugar e retornado a um chamador que pode não se importar com os detalhes da implementação. Vamos mudar o seu exemplo do IBox para um Shape. Agora podemos ter implementações de Shape como Rectangle, Circle, Triangle, etc. As implementações dos métodos getArea () e getSize () serão completamente diferentes para cada classe concreta.

Agora você pode usar uma fábrica com uma variedade de métodos createShape (params) que retornarão uma Shape apropriada dependendo dos parâmetros transmitidos. Obviamente, a fábrica saberá que tipo de Shape está sendo criado, mas o chamador não terá para se preocupar se é um círculo, um quadrado ou assim por diante.

Agora, imagine que você tenha uma variedade de operações que você precisa realizar em suas formas. Talvez você precise classificá-los por área, definir todos eles para um novo tamanho e, em seguida, exibi-los em uma interface do usuário. As formas são todas criadas pela fábrica e depois podem ser passadas para as classes Classificador, Sizer e Display com muita facilidade. Se você precisar adicionar uma classe hexagonal em algum momento no futuro, não será necessário alterar nada além da fábrica. Sem a interface, adicionar outra forma se torna um processo muito confuso.


O propósito das interfaces é abstração ou desacoplamento da implementação.

Se você introduzir uma abstração em seu programa, não se importará com as possíveis implementações. Você está interessado no que pode e não como , e usa uma interface para expressar isso em Java.


Interfaces permitem que linguagens com tipagem estática suportem polimorfismo. Um purista Orientado a Objetos insistiria que uma linguagem deveria fornecer herança, encapsulamento, modularidade e polimorfismo para ser uma linguagem Orientada a Objetos com todos os recursos. Em linguagens tipificadas dinamicamente - ou em linguagem de tipo duck (como Smalltalk), o polimorfismo é trivial; no entanto, em linguagens com tipagem estática (como Java ou C #), o polimorfismo está longe de ser trivial (de fato, na superfície, parece estar em desacordo com a noção de tipagem forte).

Deixe-me demonstrar:

Em uma linguagem tipificada dinamicamente (como o Smalltalk), todas as variáveis ​​são referências a objetos (nada menos e nada mais). Então, no Smalltalk, eu posso fazer isso:

|anAnimal|    
anAnimal := Pig new.
anAnimal makeNoise.

anAnimal := Cow new.
anAnimal makeNoise.

Esse código:

  1. Declara uma variável local chamada anAnimal (observe que NÃO especificamos o TYPE da variável - todas as variáveis ​​são referências a um objeto, nem mais nem menos).
  2. Cria uma nova instância da classe chamada "Pig"
  3. Atribui essa nova instância de Pig à variável anAnimal.
  4. Envia a mensagem makeNoise para o porco.
  5. Repete a coisa toda usando uma vaca, mas atribuindo-a à mesma variável exata do Pig.

O mesmo código Java seria algo parecido com isso (supondo que Duck e Cow são subclasses de Animal:

Animal anAnimal = new Pig();
duck.makeNoise();

anAnimal = new Cow();
cow.makeNoise();

Tudo bem, até introduzirmos a classe Vegetable. Os vegetais têm o mesmo comportamento que Animal, mas não todos. Por exemplo, tanto Animal quanto Vegetal podem crescer, mas claramente os vegetais não fazem barulho e os animais não podem ser colhidos.

Em Smalltalk, podemos escrever isto:

|aFarmObject|
aFarmObject := Cow new.
aFarmObject grow.
aFarmObject makeNoise.

aFarmObject := Corn new.
aFarmObject grow.
aFarmObject harvest.

Isso funciona perfeitamente em Smalltalk porque ele é digitado por pato (se ele anda como um pato e se comporta como um pato - é um pato). Nesse caso, quando uma mensagem é enviada para um objeto, uma pesquisa é executada lista de métodos do receptor, e se um método correspondente for encontrado, será chamado. Se não, algum tipo de exceção NoSuchMethodError é lançado - mas tudo é feito em tempo de execução.

Mas em Java, uma linguagem com tipagem estática, que tipo podemos atribuir à nossa variável? O milho precisa herdar de Vegetable, para suportar o crescimento, mas não pode herdar de Animal, porque não faz barulho. A vaca precisa herdar de Animal para apoiar o makeNoise, mas não pode herdar de Vegetable porque não deve implementar a colheita. Parece que precisamos de herança múltipla - a capacidade de herdar de mais de uma classe. Mas isso acaba sendo um recurso de linguagem bastante difícil por causa de todos os casos de borda que aparecem (o que acontece quando mais de uma superclasse paralela implementa o mesmo método ?, etc.)

Junto vêm interfaces ...

Se fizermos as classes Animal e Vegetal, com cada implementação do Growable, podemos declarar que nossa vaca é animal e nosso milho é vegetal. Também podemos declarar que tanto Animal quanto Vegetal são Cultivados. Isso nos permite escrever isso para crescer tudo:

List<Growable> list = new ArrayList<Growable>();
list.add(new Cow());
list.add(new Corn());
list.add(new Pig());

for(Growable g : list) {
   g.grow();
}

E isso nos permite fazer isso, fazer barulhos de animais:

List<Animal> list = new ArrayList<Animal>();
list.add(new Cow());
list.add(new Pig());
for(Animal a : list) {
  a.makeNoise();
}

A vantagem da linguagem duck-typed é que você obtém um polimorfismo muito bom: tudo o que uma classe precisa fazer para fornecer um comportamento é fornecer o método. Enquanto todos jogarem bem e só enviarem mensagens que correspondam a métodos definidos, tudo estará bem. A desvantagem é que o tipo de erro abaixo não é capturado até o tempo de execução:

|aFarmObject|
aFarmObject := Corn new.
aFarmObject makeNoise. // No compiler error - not checked until runtime.

Linguagens com tipagem estática fornecem muito melhor "programação por contrato", porque elas pegam os dois tipos de erros abaixo em tempo de compilação:

// Compiler error: Corn cannot be cast to Animal.
Animal farmObject = new Corn();  
farmObject makeNoise();

-

// Compiler error: Animal doesn't have the harvest message.
Animal farmObject = new Cow();
farmObject.harvest(); 

Então ... para resumir:

  1. A implementação da interface permite que você especifique que tipos de objetos objetos podem fazer (interação) e herança de classes permite que você especifique como as coisas devem ser feitas (implementação).

  2. Interfaces nos dão muitos dos benefícios do polimorfismo "verdadeiro", sem sacrificar a verificação do tipo de compilador.


Diferença entre a classe Abstact e a interface

  1. Classes abstratas versus interfaces no Java 8
  2. Diferença conceitual:

Métodos padrão de interface no Java 8

  1. O que é o método padrão?
  2. Erro de compilação do método ForEach resolvido usando o método padrão
  3. Método padrão e vários problemas de ambiguidade de herança
  4. Pontos importantes sobre os métodos padrão da interface java:

Método Estático da Interface Java

  1. Método estático da interface Java, exemplo de código, método estático vs. método padrão
  2. Pontos importantes sobre o método estático da interface java:

Interfaces Funcionais Java

Classes abstratas versus interfaces no Java 8

As mudanças na interface do Java 8 incluem métodos estáticos e métodos padrão nas interfaces. Antes do Java 8, poderíamos ter apenas declarações de método nas interfaces. Mas a partir do Java 8, podemos ter métodos padrão e métodos estáticos nas interfaces.

Depois de introduzir o Método Padrão, parece que as interfaces e as classes abstratas são as mesmas. No entanto, eles ainda são conceitos diferentes no Java 8.

A classe abstrata pode definir o construtor. Eles são mais estruturados e podem ter um estado associado a eles. Em contraste, o método padrão pode ser implementado apenas nos termos de invocação de outros métodos de interface, sem referência ao estado de uma implementação específica. Portanto, ambos usam para propósitos diferentes e a escolha entre dois realmente depende do contexto do cenário.

Diferença conceitual:

As classes abstratas são válidas para implementações esqueléticas (ou seja, parciais) de interfaces, mas não devem existir sem uma interface correspondente.

Então, quando as classes abstratas são efetivamente reduzidas para serem de baixa visibilidade, implementações esqueléticas de interfaces, os métodos padrão podem levar isso também? Decididamente: não! A implementação de interfaces quase sempre requer algumas ou todas as ferramentas de criação de classe que os métodos padrão não possuem. E se alguma interface não, é claramente um caso especial, que não deve levar você ao erro.

Métodos padrão de interface no Java 8

O Java 8 introduz o novo recurso “ Método padrão ” ou (métodos Defensor), que permite ao desenvolvedor adicionar novos métodos às interfaces sem interromper a implementação existente dessas interfaces. Ele fornece flexibilidade para permitir a implementação de definição de interface, que usará como padrão na situação em que uma classe concreta falha ao fornecer uma implementação para esse método.

Vamos considerar um pequeno exemplo para entender como funciona:

public interface OldInterface {
    public void existingMethod();
 
    default public void newDefaultMethod() {
        System.out.println("New default method"
               + " is added in interface");
    }
}

A classe seguinte irá compilar com sucesso no Java JDK 8,

public class OldInterfaceImpl implements OldInterface {
    public void existingMethod() {
     // existing implementation is here…
    }
}

Se você criar uma instância de OldInterfaceImpl:

OldInterfaceImpl obj = new OldInterfaceImpl ();
// print “New default method add in interface”
obj.newDefaultMethod(); 

Método Padrão:

Os métodos padrão nunca são finais, não podem ser sincronizados e não podem substituir os métodos do objeto. Eles são sempre públicos, o que limita severamente a capacidade de escrever métodos curtos e reutilizáveis.

Os métodos padrão podem ser fornecidos para uma interface sem afetar a implementação de classes, pois inclui uma implementação. Se cada método adicionado em uma interface definida com implementação, nenhuma classe de implementação é afetada. Uma classe de implementação pode substituir a implementação padrão fornecida pela interface.

Os métodos padrão permitem adicionar novas funcionalidades às Interfaces existentes sem interromper a implementação dessas Interfaces.

Quando estendemos uma interface que contém um método padrão, podemos executar o seguinte,

  1. Não substitua o método padrão e herdará o método padrão.
  2. Sobrescreva o método padrão similar a outros métodos que sobrescrevemos na subclasse.
  3. Redeclare o método padrão como abstract, que força a subclasse a sobrescrevê-lo.

Erro de compilação do método ForEach resolvido usando o método padrão

Para o Java 8, as coleções do JDK foram estendidas e o método forEach foi adicionado à coleção inteira (que funciona em conjunto com lambdas). Com o caminho convencional, o código se parece abaixo,

public interface Iterable<T> {
    public void forEach(Consumer<? super T> consumer);
}

Como esse resultado cada classe de implementação com erros de compilação, portanto, um método padrão adicionado com uma implementação necessária para que a implementação existente não deve ser alterada.

A interface Iterable com o método padrão está abaixo,

public interface Iterable<T> {
    public default void forEach(Consumer
                   <? super T> consumer) {
        for (T t : this) {
            consumer.accept(t);
        }
    }
}

O mesmo mecanismo foi usado para adicionar o Stream na interface do JDK sem violar as classes de implementação.

Método padrão e vários problemas de ambiguidade de herança

Como o Java Class pode implementar várias Interfaces e cada Interface pode definir o método padrão com a mesma assinatura de método, portanto, os métodos herdados podem entrar em conflito uns com os outros.

Considere abaixo o exemplo,

public interface InterfaceA {  
       default void defaultMethod(){  
           System.out.println("Interface A default method");  
    }  
}
 
public interface InterfaceB {
   default void defaultMethod(){
       System.out.println("Interface B default method");
   }
}
 
public class Impl implements InterfaceA, InterfaceB  {
}

O código acima falhará ao compilar com o seguinte erro,

java: class Impl herda padrões não relacionados para defaultMethod () dos tipos InterfaceA e InterfaceB

Para corrigir essa classe, precisamos fornecer a implementação do método padrão:

public class Impl implements InterfaceA, InterfaceB {
    public void defaultMethod(){
    }
}

Além disso, se quisermos invocar a implementação padrão fornecida por qualquer superinterface em vez de nossa própria implementação, podemos fazê-lo da seguinte maneira:

public class Impl implements InterfaceA, InterfaceB {
    public void defaultMethod(){
        // existing code here..
        InterfaceA.super.defaultMethod();
    }
}

Podemos escolher qualquer implementação padrão ou ambas como parte do nosso novo método.

Pontos importantes sobre os métodos padrão da interface java:

  1. Os métodos padrão da interface Java nos ajudarão a estender as interfaces sem ter o medo de quebrar as classes de implementação.
  2. Os métodos padrão da interface Java têm uma ponte nas diferenças entre interfaces e classes abstratas.
  3. Os métodos padrão da interface Java 8 nos ajudarão a evitar classes de utilitários, como todo o método de classe Collections pode ser fornecido nas próprias interfaces.
  4. Os métodos padrão da interface Java nos ajudarão a remover classes de implementação de base, podemos fornecer implementação padrão e as classes de implementação podem escolher qual delas substituir.
  5. Uma das principais razões para introduzir métodos padrão em interfaces é aprimorar a API Collections no Java 8 para suportar expressões lambda.
  6. Se alguma classe na hierarquia tiver um método com a mesma assinatura, os métodos padrão se tornarão irrelevantes. Um método padrão não pode substituir um método de java.lang.Object. O raciocínio é muito simples, porque Objeto é a classe base para todas as classes java. Portanto, mesmo se tivermos métodos de classe de objeto definidos como métodos padrão em interfaces, isso será inútil porque o método de classe de objeto sempre será usado. É por isso que, para evitar confusão, não podemos ter métodos padrão que estão sobrescrevendo os métodos da classe Object.
  7. Os métodos padrão da interface Java também são referidos como métodos Defender ou métodos de extensão virtual.

Link de Recurso:

  1. Interface com métodos padrão vs Classe abstrata no Java 8
  2. Classe abstrata versus interface na era do JDK 8
  3. Evolução da interface através de métodos de extensão virtual

Método Estático da Interface Java

Método estático da interface Java, exemplo de código, método estático vs. método padrão

O método estático da interface Java é semelhante ao método padrão, exceto que não podemos substituí-los nas classes de implementação. Esse recurso nos ajuda a evitar resultados indesejados, caso haja pouca implementação nas classes de implementação. Vamos analisar isso com um exemplo simples.

public interface MyData {

    default void print(String str) {
        if (!isNull(str))
            System.out.println("MyData Print::" + str);
    }

    static boolean isNull(String str) {
        System.out.println("Interface Null Check");

        return str == null ? true : "".equals(str) ? true : false;
    }
}

Agora vamos ver uma classe de implementação que está tendo o método isNull () com implementação deficiente.

public class MyDataImpl implements MyData {

    public boolean isNull(String str) {
        System.out.println("Impl Null Check");

        return str == null ? true : false;
    }

    public static void main(String args[]){
        MyDataImpl obj = new MyDataImpl();
        obj.print("");
        obj.isNull("abc");
    }
}

Note que isNull (String str) é um método de classe simples, não está sobrescrevendo o método da interface. Por exemplo, se adicionarmos a anotação @Override ao método isNull (), isso resultará em erro do compilador.

Agora, quando executarmos o aplicativo, obteremos a seguinte saída.

Verificação nula da interface

Verificação nula de implante

Se fizermos o método de interface de estático para padrão, obteremos a seguinte saída.

Verificação nula de implante

Impressão de MyData ::

Verificação nula de implante

O método estático da interface Java é visível somente para métodos de interface, se removermos o método isNull () da classe MyDataImpl, não poderemos usá-lo para o objeto MyDataImpl. No entanto, como outros métodos estáticos, podemos usar métodos estáticos de interface usando o nome da classe. Por exemplo, uma declaração válida será:

boolean result = MyData.isNull("abc");

Pontos importantes sobre o método estático da interface java:

  1. O método estático da interface Java faz parte da interface, não podemos usá-lo para objetos de classe de implementação.
  2. Os métodos estáticos da interface Java são bons para fornecer métodos de utilitário, por exemplo, verificação nula, coleta de classificação etc.
  3. O método estático da interface Java nos ajuda a fornecer segurança, não permitindo que as classes de implementação os sobrescrevam.
  4. Nós não podemos definir o método estático da interface para os métodos da classe Object, nós obteremos o erro do compilador como "Este método estático não pode esconder o método da instância do objeto". Isso ocorre porque não é permitido em java, já que Object é a classe base de todas as classes e não podemos ter um método estático de nível de classe e outro método de instância com a mesma assinatura.
  5. Podemos usar métodos estáticos da interface java para remover classes de utilitários como Coleções e mover todos os seus métodos estáticos para a interface correspondente, que seria fácil de encontrar e usar.

Interfaces Funcionais Java

Antes de concluir o post, gostaria de fornecer uma breve introdução às interfaces funcionais. Uma interface com exatamente um método abstrato é conhecida como Interface Funcional.

Uma nova anotação @FunctionalInterface foi introduzida para marcar uma interface como Interface Funcional. @FunctionalInterface anotação @FunctionalInterface é uma facilidade para evitar a adição acidental de métodos abstratos nas interfaces funcionais. É opcional, mas é uma boa prática usá-lo.

Interfaces funcionais são esperadas e muito procuradas pelo Java 8 porque nos permite usar expressões lambda para instanciá-las. Um novo pacote java.util.function com várias interfaces funcionais é incluído para fornecer tipos de destino para expressões lambda e referências a métodos. Vamos olhar para interfaces funcionais e expressões lambda nos posts futuros.

Localização do Recurso:

  1. Mudanças na interface do Java 8 - método estático, método padrão




java oop language-features interface