Existe mais em uma interface do que ter os methods corretos

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 class 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 dele, 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, o único propósito das interfaces é garantir que a class que implementa uma interface tenha os methods corretos, conforme descrito por uma interface? Ou há algum outro uso de interfaces?

    Interfaces são uma maneira de tornar seu código mais flexível. O que você faz é isto:

     Ibox myBox=new Rectangle(); 

    Então, mais tarde, se você decidir usar um tipo diferente de checkbox (talvez haja outra biblioteca, com um tipo de checkbox melhor), mude seu código para:

     Ibox myBox=new OtherKindOfBox(); 

    Uma vez que você se acostumar com isso, você descobrirá que é uma ótima maneira (realmente essencial) de trabalhar.

    Outra razão é, por exemplo, se você deseja criar uma lista de checkboxs e executar alguma operação em cada uma delas, mas deseja que a lista contenha diferentes tipos de checkboxs. Em cada checkbox você poderia fazer:

     myBox.close() 

    (supondo que o IBox tenha um método close ()) mesmo que a class real do myBox mude dependendo de qual checkbox você está na iteração.

    O que torna as interfaces úteis não é o fato de que “você pode mudar de ideia e usar uma implementação diferente posteriormente e só precisa alterar o único local onde o object é criado”. Isso não é um 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 methods ú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 class que implemente as interfaces List – não apenas ArrayList e LinkedList , mas também classs 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 a você para suportar suas classs.

    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.

    Um dos muitos usos que tenho lido é onde é difícil sem usar várias interfaces de inheritance 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 classs 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 tenha a class 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 { } 

    O objective das interfaces é o polymorphism , também conhecido como substituição de tipo . Por exemplo, dado o seguinte método:

     public void scale(IBox b, int i) { b.setSize(b.getSize() * i); } 

    Ao chamar o método de scale , você pode fornecer qualquer valor que seja de um tipo que implemente a interface do IBox . Em outras palavras, se o Rectangle e o Square implementarem o IBox , você poderá fornecer um Rectangle ou um Square sempre que um IBox for esperado.

    Interfaces permitem que linguagens com tipagem estática suportem polymorphism. Um purista Orientado a Objetos insistiria que uma linguagem deveria fornecer inheritance, encapsulamento, modularidade e polymorphism para ser uma linguagem Orientada a Objetos com todos os resources. Em linguagens tipificadas dinamicamente – ou em linguagem duck type – (como Smalltalk), o polymorphism é trivial; no entanto, em linguagens com tipagem estática (como Java ou C #), o polymorphism 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 variables ​​são referências a objects (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 (note que NÃO especificamos o TYPE da variável – todas as variables ​​são referências a um object, nem mais nem menos).
    2. Cria uma nova instância da class 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 subclasss de Animal:

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

    Tudo bem, até introduzirmos a class 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 isso:

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

    Isso funciona perfeitamente bem em Smalltalk porque é typescript por pato (se ele anda como um pato e grita como um pato – é um pato). Nesse caso, quando uma mensagem é enviada para um object, uma pesquisa é executada em lista de methods 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 inheritance múltipla – a capacidade de herdar de mais de uma class. 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 superclass paralela implementa o mesmo método ?, etc.)

    Junto vêm interfaces …

    Se fizermos as classs Animal e Vegetal, com cada implementação de 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 list = new ArrayList(); 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 list = new ArrayList(); 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 polymorphism muito bom: tudo que uma class precisa fazer para fornecer um comportamento é fornecer o método. Enquanto todos jogarem bem e só enviarem mensagens que correspondam aos methods definidos, tudo estará bem. A desvantagem é que o tipo de erro abaixo não é detectado 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 eles vão pegar os dois tipos de erros abaixo em tempo de compilation:

     // 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 quais tipos de objects objects podem fazer (interação) e inheritance de classs permite que você especifique como as coisas devem ser feitas (implementação).

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

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

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

    Agora sua function 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 class! 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 methods / campos do IBox e não os (possivelmente existentes) outros methods do Rectangle .

    Eu acho que você entende tudo 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 object 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 class concreta é conhecida.

    Onde Interfaces são úteis é quando um object 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 de IBox para uma forma. Agora podemos ter implementações de Shape como Rectangle, Circle, Triangle, etc. As implementações dos methods getArea () e getSize () serão completamente diferentes para cada class concreta.

    Agora você pode usar uma fábrica com uma variedade de methods 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 exibi-los em uma interface do usuário. As formas são todas criadas pela fábrica e, em seguida, podem ser passadas para as classs Sorter, Sizer e Display com muita facilidade. Se você precisar adicionar uma class hexagonal em algum momento no futuro, não precisará alterar nada além da fábrica. Sem a interface, adicionar outra forma se torna um processo muito confuso.

    você poderia fazer

     Ibox myBox = new Rectangle(); 

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

    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 pugs como 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 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 classs Pug e Lab para implementar os comportamentos do cão. Podemos dizer que um Pug é um cão 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 } 

    Eu 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 vale a pena. A turma do Kennel se torna muito mais simples. Eu preciso de apenas um array e um método addDog. Ambos trabalharão com qualquer object que seja um cachorro; isto é, objects 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 oferece a capacidade de especificar um conjunto de comportamentos que todas as classs que implementam a interface compartilharão em comum. Conseqüentemente, podemos definir variables ​​e collections (como arrays) que não precisam saber de antemão que tipo de object específico elas conterão, apenas que elas conterão objects que implementam a interface.

    Interfaces

    Há uma série de situações na engenharia de software quando é importante que grupos diferentes de programadores concordem com um “contrato” que explique como o seu software interage. Cada grupo deve ser capaz de escrever seu código sem qualquer conhecimento de como o código do outro grupo é escrito. De um modo geral, as interfaces são esses contratos.

    Por exemplo, imagine uma sociedade futurista em que carros robóticos controlados por computador transportam passageiros pelas ruas da cidade sem um operador humano. Os fabricantes de automóveis escrevem software (Java, é claro) que opera o automóvel – pare, inicie, acelere, vire à esquerda e assim por diante. Outro grupo industrial, fabricantes de instrumentos de orientação eletrônica, fabrica sistemas de computador que recebem dados de posição GPS (Global Positioning System) e transmissão sem fio de condições de tráfego e usam essa informação para dirigir o carro.

    Os fabricantes de automóveis devem publicar uma interface padrão do setor que explique detalhadamente quais methods podem ser invocados para fazer o carro se mover (qualquer carro, de qualquer fabricante). Os fabricantes de orientação podem então criar um software que invoque os methods descritos na interface para comandar o carro. Nenhum grupo industrial precisa saber como o software do outro grupo é implementado. De fato, cada grupo considera seu software altamente proprietário e se reserva o direito de modificá-lo a qualquer momento, desde que continue a aderir à interface publicada.

    Um ótimo exemplo de como as interfaces são usadas está na estrutura Coleções. Se você escrever uma function 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 function 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.

    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 object 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 o objective de ter interfaces é tê-las como indistinguíveis dos tipos de class).

    Não se esqueça que em uma data posterior você pode pegar uma class existente e implementá- IBox , e ela ficará disponível para todo o seu código.

    Isso se torna um pouco mais claro se as interfaces forem nomeadas -able . por exemplo

     public interface Saveable { .... public interface Printable { .... 

    etc. (Os esquemas de nomeação nem sempre funcionam, por exemplo, não sei se o Boxable é apropriado aqui)

    O único propósito das interfaces é garantir que a class que implementa uma interface tenha os methods corretos, conforme descrito por uma interface? Ou há algum outro uso de interfaces?

    Estou atualizando a resposta com novos resources 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. methods padrão
    3. methods estáticos
    4. definições constantes.

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

    Usos da interface :

    1. Para definir um contrato
    2. Para vincular classs não relacionadas com tem um recurso (por exemplo, classs implementando interface Serializable pode ou não ter qualquer relação entre eles, exceto implementar essa interface
    3. Para fornecer implementação intercambiável, por exemplo, padrão de estratégia
    4. Os methods 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 methods auxiliares em suas bibliotecas com methods estáticos (é possível manter methods estáticos específicos para uma interface na mesma interface em vez de em uma class separada)

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

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

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

    Dê uma olhada na página de documentação para entender os novos resources adicionados no java 8: methods padrão e methods estáticos .

    O objective das interfaces é abstração ou dissociação 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.

    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 methods que não se importam com “abrir” ou “resize” ainda podem usar suas classs (talvez porque se importem com quantos pixels são necessários para exibir algo em uma canvas).

    Interfaces onde uma fetature adicionada ao java para permitir inheritance múltipla. Os desenvolvedores de Java embora / perceberam que ter inheritance múltipla era um recurso “perigoso”, por isso surgiu a idéia de uma interface.

    inheritance múltipla é perigosa porque você pode ter uma class 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 }
    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();
    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 methods de class … 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.

    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 class OperatingSystem que pode chamar methods de interface como saveData apenas passando uma instância de class 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..."); } }