Gibt es mehr zu einer Schnittstelle als die richtigen Methoden

159

Nehmen wir also an, ich habe diese Schnittstelle:

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

Und ich habe eine Klasse, die es implementiert:

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

Wenn ich die Schnittstelle IBox verwenden wollte, kann ich keine Instanz davon erstellen, wie folgt:

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

richtig? Also müsste ich das tatsächlich tun:

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

Wenn dies zutrifft, besteht der einzige Zweck von Schnittstellen darin, sicherzustellen, dass die Klasse, die eine Schnittstelle implementiert, die richtigen Methoden enthält, wie von einer Schnittstelle beschrieben. Oder gibt es eine andere Verwendung von Schnittstellen?

Klicken Sie auf Upvote
quelle
2
Denken Sie daran, dass Schnittstellen nicht spezifisch für Java sind. Alle OOP-Sprachen haben sie in der einen oder anderen Form, obwohl sie nicht immer so explizit wie Java definiert sind.
Herms
2
Technisch gesehen haben alle stark typisierten OOP-Sprachen sie in der einen oder anderen Form. Untypisierte oder ententypisierte Sprachen haben kein ähnliches Konzept.
Jared
1
@Jared Verwechseln Sie nicht starkes Tippen mit statischem Tippen und "untypisiert" mit dynamisch tippt?
Eljenso
Polymorphismus kann auch über Schnittstellen erreicht werden. Überprüfen Sie den letzten Abschnitt dieser Seite codenuggets.com/2014/06/20/java-interface
Jeff

Antworten:

143

Schnittstellen sind eine Möglichkeit, Ihren Code flexibler zu gestalten. Was Sie tun, ist Folgendes:

Ibox myBox=new Rectangle();

Wenn Sie später entscheiden, dass Sie eine andere Art von Box verwenden möchten (möglicherweise gibt es eine andere Bibliothek mit einer besseren Art von Box), wechseln Sie Ihren Code zu:

Ibox myBox=new OtherKindOfBox();

Sobald Sie sich daran gewöhnt haben, werden Sie feststellen, dass dies eine großartige (eigentlich unverzichtbare) Arbeitsweise ist.

Ein weiterer Grund ist beispielsweise, wenn Sie eine Liste von Feldern erstellen und für jedes eine Operation ausführen möchten, die Liste jedoch verschiedene Arten von Feldern enthalten soll. Auf jeder Box können Sie Folgendes tun:

myBox.close()

(vorausgesetzt, IBox hat eine close () -Methode), obwohl sich die tatsächliche Klasse von myBox ändert, je nachdem, in welchem ​​Feld Sie sich in der Iteration befinden.

Morgancodes
quelle
54
Diese Antwort enthält nichts, was nur für Java-Schnittstellen gilt . Gleiches gilt gleichermaßen für abstrakte oder sogar konkrete Klassen. Ich würde eine gute Antwort erwarten, um die Fähigkeit zu erwähnen, mehrere Schnittstellen zu implementieren , und wann / warum dies nützlich wäre.
Rogério
16
Wie wurde dies als Antwort ausgewählt? Es ist eine kurze Beschreibung, warum Polymorphismus nützlich ist, aber wie im obigen Poster erwähnt, würde ich eine bessere Erklärung mehrerer Schnittstellen erwarten und noch wichtiger, wenn es angemessen ist, eine Schnittstelle gegenüber einer abstrakten Klasse zu verwenden.
Trevorkavanaugh
2
Dies hat sehr wenig mit der Erklärung von Schnittstellen und allem zu tun, was mit den Grundlagen des Polymorphismus zu tun hat
A-Entwickler-hat-keinen-Namen
123

Was Schnittstellen nützlich macht, ist nicht die Tatsache, dass "Sie Ihre Meinung ändern und später eine andere Implementierung verwenden können und nur den einen Ort ändern müssen, an dem das Objekt erstellt wird". Das ist kein Problem.

Der eigentliche Punkt liegt bereits im Namen: Sie definieren eine Schnittstelle , die jeder implementieren kann, um den gesamten Code zu verwenden, der auf dieser Schnittstelle ausgeführt wird. Das beste Beispiel java.util.Collectionsbietet alle Arten von nützlichen Methoden, die ausschließlich auf Schnittstellen wie sort()oder reverse()für arbeiten List. Der Punkt hier ist, dass dieser Code jetzt verwendet werden kann, um jede Klasse zu sortieren oder umzukehren , die die ListSchnittstellen implementiert - nicht nur ArrayListund LinkedList, sondern auch Klassen, die Sie selbst schreiben. Diese können auf eine Weise implementiert werden, die sich die Leute, die geschrieben haben, java.util.Collectionsnie vorgestellt haben.

Auf die gleiche Weise können Sie Code schreiben, der auf bekannten oder von Ihnen definierten Schnittstellen ausgeführt wird, und andere Personen können Ihren Code verwenden, ohne Sie bitten zu müssen, ihre Klassen zu unterstützen.

Eine andere häufige Verwendung von Schnittstellen sind Rückrufe. Beispiel: java.swing.table.TableCellRenderer , mit dem Sie beeinflussen können, wie eine Swing-Tabelle die Daten in einer bestimmten Spalte anzeigt. Sie implementieren diese Schnittstelle, übergeben eine Instanz an die JTableund irgendwann während des Renderns der Tabelle wird Ihr Code aufgerufen, um seine Aufgaben zu erledigen.

Michael Borgwardt
quelle
9
Das ist eine ziemlich gute Antwort, ich mag es, wenn Sie Beispiele aus Java-Paketklassen geben ...
Owais Qureshi
1
Ich mochteyou can write code that operates on well-known interfaces, or interfaces you define
Manish Kumar
Warten Sie einen Moment ... Was Schnittstellen nützlich macht, ist nicht [die Fähigkeit, eine beliebige Implementierung zu verwenden], sondern [die Fähigkeit, eine beliebige Implementierung zu verwenden, die Sie mögen]? Den umgekehrten Fall zu erwähnen , ist jedoch ein guter Punkt.
Powerslave
4
@Powerslave: paraphrasieren Sie es eher wie Was Schnittstellen nützlich macht, ist nicht [die Fähigkeit, Code zu schreiben, bei dem Sie nur eine Zeile ändern müssen, wenn Sie die Implementierung ändern], sondern [die Fähigkeit, Code zu schreiben, bei dem Sie nicht einmal eine Implementierung angeben alles].
Michael Borgwardt
@ MichaelBorgwardt Das klingt viel besser. :) Danke fürs klarstellen!
Powerslave
119

Eine der vielen Anwendungen, die ich gelesen habe, ist, wo es ohne Java-Schnittstellen mit Mehrfachvererbung schwierig ist:

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

Stellen Sie sich nun einen Fall vor, in dem:

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

aber,

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

Besseres Design wäre:

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

Animal verfügt nicht über die Methode ________ () und wird stattdessen wie folgt in eine Schnittstelle eingefügt:

interface Chewable {
void chew();
}

und lassen Sie die Reptilienklasse dies implementieren und nicht Vögel (da Vögel nicht kauen können):

class Reptile extends Animal implements Chewable { } 

und bei Vögeln einfach:

class Bird extends Animal { }
peevesy
quelle
6
@ CHEBURASHKA Und schlechte Benennung. Wenn Reptile"kaut", dann ist selbst nicht "kaubar". Die Konvention der (manchmal) Benennung von Schnittstellen Whateverable sollte nur dort angewendet werden, wo es durchaus Sinn macht. Die Benennung der Schnittstelle Predatorwäre hier angemessener.
Powerslave
6
@ Powerslave Es ist imho richtig, ein Reptil kann "kauen" / "kauen". Ein Falke ist ein Raubtier, kann aber immer noch nicht kauen ... nur Nitpicking, aber "kaubar" kann in der Dokumentation der Schnittstelle besser definiert werden.
Madmenyo
Hervorragend. Sehr gut erklärt. Danke dir..!
Gurusinghe
1
Ich verstehe es nicht Alles, was so scheint, ist, dass Schnittstellen eine gute Möglichkeit sind, um sicherzustellen, dass Sie in jeder Klasse, die sie implementiert, dieselben Methoden implementieren (z. B. so, dass ich nicht die dumme Wahl einer Bird-Klasse mit run () und einer Dog-Klasse treffen kann mit runn () - sie sind alle gleich). Aber konnte ich nicht dasselbe erreichen, indem ich darauf achtete und dafür sorgte, dass meine Klassen dieselben Methodenformate / -strukturen hatten? Es scheint wirklich so, als ob Schnittstellen nur sicherstellen, dass der Programmierer nicht vergisst. Außerdem scheinen mir Schnittstellen keine Zeit zu sparen. Ich muss noch die Methode in jeder Klasse definieren, die sie implementiert.
Alex G
@AlexG - ich habe einem der vielen Verwendungszwecke erzählt, Alter :) Es gibt noch mehr, wir hatten kaum die Oberfläche zerkratzt, um die Frage auf einfache Weise zu beantworten!
Peevesy
47

Der Zweck von Schnittstellen ist Polymorphismus , auch bekannt als Typensubstitution . Zum Beispiel mit der folgenden Methode:

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

Beim Aufrufen der scaleMethode können Sie einen beliebigen Wert angeben, der vom Typ ist, der die IBoxSchnittstelle implementiert . Mit anderen Worten, wenn Rectangleund Squarebeide implementieren IBox, können Sie entweder a Rectangleoder a Squareangeben, wo immer ein IBoxerwartet wird.

Apocalisp
quelle
8
Warum ist der Zweck von Schnittstellenpolymorphismus, wenn ich dies in Java bereits durch Unterklassen und Überschreiben von Methoden erreichen kann?
Eljenso
1
Es ist dasselbe, außer dass Schnittstellen jede Implementierung weglassen müssen. Klassen können daher mehr als eine Schnittstelle implementieren.
Apocalisp
4
Hey, ich habe nie gesagt, dass Java irgendeine konzeptionelle Integrität hat. Die Typersetzung ist der Zweck aller Untertypen. Java hat zufällig mehr als einen Subtypisierungsmechanismus, von denen keiner besonders gut ist.
Apocalisp
1
Ich habe auch nie etwas über konzeptionelle Integrität gesagt. Aber lass uns weitermachen. Wenn Sie jede IBox mit Ihrer Methode skalieren können, sollte es sich dann nicht um eine in IBox deklarierte Operation handeln: IBox.scale (int)?
Eljenso
1
Wir möchten Integer nicht mit IBox koppeln, deshalb machen wir es nicht zu einer Methode für Integer. Die Anzahl der Methoden auf einer Schnittstelle hängt von der Konsistenz und Kohäsion der Abstraktion ab, die sie ausdrückt, und nicht davon, wie umständlich die Implementierung wäre. Trotzdem danke für deine Antworten Apo.
Eljenso
33

Mithilfe von Schnittstellen können statisch typisierte Sprachen den Polymorphismus unterstützen. Ein objektorientierter Purist würde darauf bestehen, dass eine Sprache Vererbung, Kapselung, Modularität und Polymorphismus bietet, um eine voll funktionsfähige objektorientierte Sprache zu sein. In dynamisch typisierten oder ententypisierten Sprachen (wie Smalltalk) ist Polymorphismus trivial; In statisch typisierten Sprachen (wie Java oder C #) ist Polymorphismus jedoch alles andere als trivial (tatsächlich scheint er an der Oberfläche im Widerspruch zu der Vorstellung einer starken Typisierung zu stehen.)

Lassen Sie mich demonstrieren:

In einer dynamisch typisierten (oder ententypisierten) Sprache (wie Smalltalk) sind alle Variablen Verweise auf Objekte (nicht weniger und nicht mehr). In Smalltalk kann ich also Folgendes tun:

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

anAnimal := Cow new.
anAnimal makeNoise.

Dieser Code:

  1. Deklariert eine lokale Variable namens anAnimal (beachten Sie, dass wir den TYP der Variablen NICHT angeben - alle Variablen sind Verweise auf ein Objekt, nicht mehr und nicht weniger.)
  2. Erstellt eine neue Instanz der Klasse "Pig"
  3. Weist diese neue Instanz von Pig der Variablen anAnimal zu.
  4. Sendet die Nachricht makeNoisean das Schwein.
  5. Wiederholt das Ganze mit einer Kuh, weist sie jedoch genau derselben Variablen wie das Schwein zu.

Der gleiche Java-Code würde ungefähr so ​​aussehen (unter der Annahme, dass Duck und Cow Unterklassen von Animal sind:

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

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

Das ist alles schön und gut, bis wir die Klasse Gemüse einführen. Gemüse hat das gleiche Verhalten wie Tier, aber nicht alle. Zum Beispiel können sowohl Tiere als auch Gemüse wachsen, aber Gemüse macht eindeutig keinen Lärm und Tiere können nicht geerntet werden.

In Smalltalk können wir Folgendes schreiben:

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

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

Dies funktioniert in Smalltalk sehr gut, da es vom Typ Ente ist (wenn es wie eine Ente läuft und wie eine Ente quakt - es ist eine Ente). In diesem Fall wird beim Senden einer Nachricht an ein Objekt eine Suche durchgeführt die Methodenliste des Empfängers, und wenn eine übereinstimmende Methode gefunden wird, wird sie aufgerufen. Wenn nicht, wird eine Art NoSuchMethodError-Ausnahme ausgelöst - aber alles wird zur Laufzeit ausgeführt.

Aber in Java, einer statisch typisierten Sprache, welchen Typ können wir unserer Variablen zuweisen? Mais muss von Gemüse erben, um das Wachstum zu unterstützen, kann aber nicht von Tier erben, da er keinen Lärm macht. Kuh muss von Animal erben, um makeNoise zu unterstützen, kann aber nicht von Vegetable erben, da es keine Ernte implementieren sollte. Es sieht so aus, als ob wir mehrere Vererbungen benötigen - die Fähigkeit, von mehr als einer Klasse zu erben. Aufgrund all der Randfälle, die auftauchen (was passiert, wenn mehr als eine parallele Superklasse dieselbe Methode implementieren? Usw.), stellt sich heraus, dass dies eine ziemlich schwierige Sprachfunktion ist.

Es kommen Schnittstellen ...

Wenn wir Tier- und Gemüseklassen mit jeder Implementierung von Growable machen, können wir erklären, dass unsere Kuh Tier und unser Mais Gemüse ist. Wir können auch erklären, dass sowohl Tier als auch Gemüse anbaubar sind. Das lässt uns dies schreiben, um alles wachsen zu lassen:

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

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

Und damit können wir Tiergeräusche machen:

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

Der Vorteil der Sprache vom Typ Ente ist, dass Sie einen wirklich guten Polymorphismus erhalten: Alles, was eine Klasse tun muss, um Verhalten zu liefern, ist die Methode bereitzustellen. Solange alle nett spielen und nur Nachrichten senden, die den definierten Methoden entsprechen, ist alles in Ordnung. Der Nachteil ist, dass der unten stehende Fehler erst zur Laufzeit abgefangen wird:

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

Statisch typisierte Sprachen bieten eine viel bessere "vertragliche Programmierung", da sie beim Kompilieren die beiden folgenden Fehlerarten abfangen:

// 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(); 

Also ... um es zusammenzufassen:

  1. Mit der Schnittstellenimplementierung können Sie angeben, welche Arten von Dingen Objekte ausführen können (Interaktion), und mit der Klassenvererbung können Sie festlegen, wie die Aufgaben ausgeführt werden sollen (Implementierung).

  2. Schnittstellen bieten uns viele Vorteile des "echten" Polymorphismus, ohne die Überprüfung des Compilertyps zu beeinträchtigen.

Jared
quelle
2
Dies ist der Text meiner Antwort auf eine andere Frage: stackoverflow.com/questions/379282/… . Aber sie sind verwandte Antworten.
Jared
2
Darf ich fragen, wie unterscheidet eine Sprache vom Typ Ente zwischen Animal.water () (was der prüde Landwirt sagte, dass es ein Leck ist) und Plant.water (), mit dem er Pflanzen gießt? Mehrdeutigkeit ist der Feind. Jede Menge an Ausführlichkeit, die zur Überwindung von Mehrdeutigkeiten erforderlich ist, ist IMO akzeptabel.
Bill K
1
Yep..ambiguity ist der Name des Spiels mit Sprachen vom Typ Ente. Wenn Sie professionell in einer Sprache vom Typ Ente arbeiten, ist es nicht ungewöhnlich, Mitglieder (Methoden und Variablen) mit Namen zu sehen, die 50 bis 100 Zeichen lang sind.
Jared
1
Ein weiterer großer Nachteil von Sprachen mit Ententyp ist die Unfähigkeit, programmatisches Refactoring basierend auf statischer Analyse durchzuführen. Versuchen Sie, ein Smalltalk-Bild nach der Liste aller Aufrufer Ihrer printString-Methode zu fragen. Sie erhalten die Liste aller Aufrufer ALLER printString-Methoden. ...
Jared
... weil der Aufrufer von Automobile # printString nicht programmgesteuert vom Aufrufer von NearEarthOrbit # printString unterschieden werden kann.
Jared
9

Normalerweise definieren Schnittstellen die Schnittstelle, die Sie verwenden sollten (wie der Name schon sagt ;-)). Stichprobe


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

Jetzt fooakzeptiert Ihre Funktion ArrayLists, LinkedLists, ... nicht nur einen Typ.

Das Wichtigste in Java ist, dass Sie mehrere Schnittstellen implementieren können, aber nur EINE Klasse erweitern können! Stichprobe:


class Test extends Foo implements Comparable, Serializable, Formattable {
...
}
ist aber möglich

class Test extends Foo, Bar, Buz {
...
}
ist nicht!

Ihr obiger Code könnte auch sein : IBox myBox = new Rectangle();. Wichtig ist jetzt, dass myBox NUR die Methoden / Felder von IBox enthält und nicht die (möglicherweise vorhandenen) anderen Methoden von IBox Rectangle.

Johannes Weiss
quelle
1
Soll 'List' ein Schnittstellenmitglied sein?
Klicken Sie auf Upvote
1
List ist eine Schnittstelle in der Java-Sammlungsbibliothek.
rmeador
List ist eine Schnittstelle in der Standard-Java-Bibliothek ( java.sun.com/javase/6/docs/api/java/util/List.html ). Er benutzt es nur, um seinen Standpunkt zu veranschaulichen.
Michael Myers
6

Ich denke, Sie verstehen alles, was Schnittstellen tun, aber Sie stellen sich noch nicht vor, in welchen Situationen eine Schnittstelle nützlich ist.

Wenn Sie ein Objekt innerhalb eines engen Bereichs (z. B. innerhalb eines Methodenaufrufs) instanziieren, verwenden und freigeben, fügt eine Schnittstelle nichts hinzu. Wie Sie bemerkt haben, ist die konkrete Klasse bekannt.

Schnittstellen sind nützlich, wenn ein Objekt an einer Stelle erstellt und an einen Aufrufer zurückgegeben werden muss, der sich möglicherweise nicht um die Implementierungsdetails kümmert. Lassen Sie uns Ihr IBox-Beispiel in eine Form ändern. Jetzt können wir Implementierungen von Shape wie Rectangle, Circle, Triangle usw. haben. Die Implementierungen der Methoden getArea () und getSize () sind für jede konkrete Klasse völlig unterschiedlich.

Jetzt können Sie eine Factory mit einer Vielzahl von createShape (params) -Methoden verwenden, die abhängig von den übergebenen Parametern eine geeignete Shape zurückgeben. Die Factory weiß natürlich, welche Art von Shape erstellt wird, der Aufrufer jedoch nicht sich darum zu kümmern, ob es sich um einen Kreis oder ein Quadrat handelt oder so weiter.

Stellen Sie sich nun vor, Sie haben eine Vielzahl von Operationen, die Sie an Ihren Formen ausführen müssen. Möglicherweise müssen Sie sie nach Bereichen sortieren, alle auf eine neue Größe einstellen und sie dann in einer Benutzeroberfläche anzeigen. Die Formen werden alle von der Fabrik erstellt und können dann sehr einfach an die Klassen Sorter, Sizer und Display übergeben werden. Wenn Sie in Zukunft eine Sechseckklasse hinzufügen müssen, müssen Sie nur die Fabrik ändern. Ohne die Benutzeroberfläche wird das Hinzufügen einer weiteren Form zu einem sehr unübersichtlichen Prozess.

Ickster
quelle
6

du könntest es tun

Ibox myBox = new Rectangle();

Auf diese Weise verwenden Sie dieses Objekt als Ibox und es ist Ihnen egal, dass es wirklich ist Rectangle.

IAdapter
quelle
Das heißt, wir könnten so schreiben?! > Rectangle inst = new Rectangle ();
Dr.jacky
@ Mr.Hyde Wenn Sie später hinzufügen möchten, hätten SquareSie ein Problem ... Wenn Sie versuchen, es ohne Schnittstellen zu tun, können Sie dies nicht garantieren Squareund Rectanglehaben die gleichen Methoden ... dies kann zu einem Albtraum führen, wenn Sie haben eine größere Codebasis ... Denken Sie daran, Schnittstellen definieren eine Vorlage.
Kolob Canyon
6

WARUM SCHNITTSTELLE ??????

Es beginnt mit einem Hund. Insbesondere ein Mops .

Der Mops hat verschiedene Verhaltensweisen:

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; } }

Und Sie haben einen Labrador, der auch eine Reihe von Verhaltensweisen hat.

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; } }

Wir können einige Möpse und Labore machen:

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

Und wir können uns auf ihr Verhalten berufen:

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

Nehmen wir an, ich betreibe einen Hundezwinger und muss alle Hunde, die ich unterbringe, im Auge behalten. Ich muss meine Möpse und Labradore in separaten Arrays aufbewahren :

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 } }

Dies ist aber eindeutig nicht optimal. Wenn ich auch einige Pudel unterbringen möchte , muss ich meine Kennel-Definition ändern, um eine Reihe von Pudeln hinzuzufügen. Tatsächlich brauche ich für jede Art von Hund ein separates Array.

Einsicht: Sowohl Möpse als auch Labradore (und Pudel) sind Hundetypen und haben das gleiche Verhalten. Das heißt, wir können (für die Zwecke dieses Beispiels) sagen, dass alle Hunde bellen können, einen Namen haben und möglicherweise einen lockigen Schwanz haben oder nicht. Wir können eine Schnittstelle verwenden, um zu definieren, was alle Hunde tun können, aber es den spezifischen Hundetypen überlassen, diese bestimmten Verhaltensweisen zu implementieren. Die Benutzeroberfläche sagt "hier sind die Dinge, die alle Hunde tun können", sagt aber nicht, wie jedes Verhalten gemacht wird.

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

Dann ändere ich die Mops- und Laborklassen leicht, um das Hundeverhalten zu implementieren. Wir können sagen, dass ein Mops ein Hund und ein Labor ein Hund ist.

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

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

Ich kann Pugs and Labs immer noch wie zuvor instanziieren, aber jetzt bekomme ich auch einen neuen Weg, dies zu tun:

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

Dies besagt, dass d1 nicht nur ein Hund ist, sondern speziell ein Mops. Und d2 ist auch ein Hund, speziell ein Labor. Wir können die Verhaltensweisen aufrufen und sie funktionieren wie zuvor:

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

Hier zahlt sich die zusätzliche Arbeit aus. Die Kennel-Klasse wird viel einfacher. Ich benötige nur ein Array und eine addDog-Methode. Beide funktionieren mit jedem Objekt, das ein Hund ist. Das heißt, Objekte, die die Dog-Schnittstelle implementieren.

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

So verwenden Sie es:

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

Die letzte Aussage würde anzeigen: Spot Fido

Über eine Schnittstelle können Sie eine Reihe von Verhaltensweisen angeben, die alle Klassen, die die Schnittstelle implementieren, gemeinsam nutzen. Folglich können wir Variablen und Sammlungen (z. B. Arrays) definieren, die nicht im Voraus wissen müssen, welche Art von spezifischem Objekt sie enthalten, sondern nur Objekte, die die Schnittstelle implementieren.

Niranjan Kurambhatti
quelle
@niranjan kurambhatti Ich kann alle Klassen machen, um Hund zu erweitern, aber immer noch warum Schnittstelle?
Jeeva
3

Ein gutes Beispiel für die Verwendung von Schnittstellen ist das Collections-Framework. Wenn Sie eine Funktion schreiben, die a benötigt List, spielt es keine Rolle, ob der Benutzer eine Vectoroder eine ArrayListoder eine HashListoder was auch immer übergibt . Und Sie können dies auch Listan jede Funktion übergeben, die eine Collectionoder eine IterableSchnittstelle benötigt.

Dies macht Funktionen wie Collections.sort(List list)möglich möglich, unabhängig davon, wie das Listimplementiert ist.

Pennen
quelle
3

Dies ist der Grund, warum Factory Patterns und andere Kreationsmuster in Java so beliebt sind. Sie haben Recht, dass Java ohne sie keinen sofort einsatzbereiten Mechanismus für die einfache Abstraktion der Instanziierung bietet. Trotzdem erhalten Sie überall eine Abstraktion, wo Sie in Ihrer Methode kein Objekt erstellen, das den größten Teil Ihres Codes ausmachen sollte.

Abgesehen davon ermutige ich die Leute im Allgemeinen, den "IRealname" -Mechanismus zum Benennen von Schnittstellen nicht zu befolgen. Das ist eine Windows / COM-Sache, die einen Fuß in das Grab der ungarischen Notation setzt und wirklich nicht notwendig ist (Java ist bereits stark typisiert, und der springende Punkt bei Schnittstellen ist, dass sie so weit wie möglich nicht von Klassentypen zu unterscheiden sind).

Christopher Smith
quelle
1
Sie verwechseln starkes Tippen mit statischem Tippen.
Eljenso
3

Vergessen Sie nicht, dass Sie zu einem späteren Zeitpunkt eine vorhandene übernehmen können Klasse übernehmen und implementieren könnenIBox können. Sie wird dann für alle Ihre Box-fähigen Codes verfügbar.

Dies wird etwas klarer, wenn die Schnittstellen den Namen -able haben . z.B

public interface Saveable {
....

public interface Printable {
....

usw. (Namensschemata funktionieren nicht immer, zB bin ich mir nicht sicher, ob dies Boxablehier angemessen ist)

Brian Agnew
quelle
3

Der einzige Zweck von Schnittstellen besteht darin, sicherzustellen, dass die Klasse, die eine Schnittstelle implementiert, die richtigen Methoden enthält, wie sie von einer Schnittstelle beschrieben werden. Oder gibt es eine andere Verwendung von Schnittstellen?

Ich aktualisiere die Antwort mit neuen Funktionen der Benutzeroberfläche, die mit Java 8 eingeführt wurden Version eingeführt wurden.

Von der Oracle-Dokumentationsseite zur Zusammenfassung der Benutzeroberfläche :

Eine Schnittstellendeklaration kann enthalten

  1. Methodensignaturen
  2. Standardmethoden
  3. statische Methoden
  4. konstante Definitionen.

Die einzigen Methoden, die implementiert sind, sind Standardmethoden und statische Methoden.

Verwendung der Schnittstelle :

  1. Einen Vertrag definieren
  2. Das Verknüpfen von nicht verwandten Klassen mit verfügt über Funktionen (z. B. implementierende KlassenSerializable Schnittstelle implementieren, eine Beziehung zwischen ihnen haben oder nicht, außer dass diese Schnittstelle implementiert wird
  3. Um eine austauschbare Implementierung zB Strategiemuster
  4. Standardmethoden Sie den Schnittstellen Ihrer Bibliotheken neue Funktionen hinzufügen und die Binärkompatibilität mit Code sicherstellen, der für ältere Versionen dieser Schnittstellen geschrieben wurde
  5. Organisieren Sie Hilfsmethoden in Ihren Bibliotheken mit statischen Methoden (Sie können statische Methoden, die für eine Schnittstelle spezifisch sind, in derselben Schnittstelle und nicht in einer separaten Klasse aufbewahren).

Einige verwandte SE-Fragen in Bezug auf den Unterschied zwischen abstrakter Klasse und Schnittstelle und Anwendungsfälle mit Arbeitsbeispielen:

Was ist der Unterschied zwischen einer Schnittstelle und einer abstrakten Klasse?

Wie hätte ich den Unterschied zwischen einer Interface- und einer Abstract-Klasse erklären sollen?

Schauen Sie sich die Dokumentationsseite an, um die neuen Funktionen von Java 8 zu verstehen: Standardmethoden und statische Methoden .

Ravindra Babu
quelle
Ich habe das Java-8-Tag entfernt, da die Frage nichts über Java-8 stellte (und tatsächlich lange vor Java-8 gestellt wurde). Tags sind für Fragen, nicht für Antworten.
Tagir Valeev
2

Der Zweck von Schnittstellen ist die Abstraktion oder Entkopplung von der Implementierung.

Wenn Sie eine Abstraktion in Ihr Programm einführen, interessieren Sie sich nicht für die möglichen Implementierungen. Sie interessieren sich dafür, was es kann und nicht wie , und Sie verwenden ein interface, um dies in Java auszudrücken.

Eljenso
quelle
Der Zweck jeder strukturierten Programmierung ist die Abstraktion. Warum würden Sie sagen, dass der Zweck von Schnittstellen die Abstraktion ist, da ich mit Generika und Klassenzusammensetzung genau dasselbe erreichen kann?
Apocalisp
1
Wenn die gesamte strukturierte Programmierung Abstraktion ist (Ihr Anspruch), sind Schnittstellen Abstraktionen in dieser Abstraktion.
Eljenso
1

Wenn Sie über CardboardBox und HtmlBox verfügen (beide implementieren IBox), können Sie beide an jede Methode übergeben, die eine IBox akzeptiert. Auch wenn beide sehr unterschiedlich und nicht vollständig austauschbar sind, können Methoden, die sich nicht für "Öffnen" oder "Größenänderung" interessieren, Ihre Klassen dennoch verwenden (möglicherweise, weil sie sich dafür interessieren, wie viele Pixel benötigt werden, um etwas auf einem Bildschirm anzuzeigen).

Todd R.
quelle
1

Schnittstellen, bei denen Java eine Fetatur hinzugefügt wurde, um eine Mehrfachvererbung zu ermöglichen. Die Entwickler von Java erkannten jedoch, dass Mehrfachvererbung eine "gefährliche" Funktion war, weshalb sie auf die Idee einer Schnittstelle kamen.

Mehrfachvererbung ist gefährlich, da Sie möglicherweise eine Klasse wie die folgende haben:


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
}

Welches wäre die Methode, die aufgerufen werden sollte, wenn wir verwenden


   FunckyFigure.GetArea(); 

Alle Probleme werden mit Schnittstellen gelöst, weil Sie wissen, dass Sie die Schnittstellen erweitern können und dass sie keine Klassifizierungsmethoden haben. Natürlich ist der Compiler nett und sagt Ihnen, wenn Sie keine Methoden implementiert haben, aber ich denke gerne, dass dies der Fall ist ein Nebeneffekt einer interessanteren Idee.

Mandel
quelle
Möglicherweise möchten Sie in Ihrer Antwort einen Unterschied zwischen der Vererbung mehrerer Implementierungen und der Vererbung mehrerer Schnittstellen machen, da dies sonst verwirrend wird.
Eljenso
0

Hier ist mein Verständnis des Schnittstellenvorteils. Korrigieren Sie mich, wenn ich falsch liege. Stellen Sie sich vor, wir entwickeln ein Betriebssystem und ein anderes Team entwickelt die Treiber für einige Geräte. Deshalb haben wir eine Schnittstelle StorageDevice entwickelt. Wir haben zwei Implementierungen davon (FDD und HDD), die von anderen Entwicklerteams bereitgestellt werden.

Dann haben wir eine OperatingSystem-Klasse, die Schnittstellenmethoden wie saveData aufrufen kann, indem nur eine Instanz der Klasse übergeben wird, die die StorageDevice-Schnittstelle implementiert hat.

Der Vorteil hierbei ist, dass uns die Implementierung der Schnittstelle egal ist. Das andere Team erledigt die Aufgabe durch Implementierung der StorageDevice-Schnittstelle.

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...");    
    }
}
Vladimir Georgiev
quelle
Dasselbe kann mit der abstrakten Klasse StorageDevice und den FDD- und HDD-Klassen durchgeführt werden, die die StorageDevice-Klasse erweitern. Wenn wir jedoch eine abstrakte Klasse verwenden, können wir die Mehrfachvererbung nicht nutzen.
Vladimir Georgiev