538 Stimmen

Wie sollte ich den Unterschied zwischen einem Interface und einer abstrakten Klasse erklärt haben?

In einem meiner Interviews wurde ich gebeten, den Unterschied zwischen einem Interface und einer abstrakten Klasse zu erklären.

Hier ist meine Antwort:

Methoden eines Java-Interfaces sind implizit abstrakt und können nicht implementiert werden. Eine Java-abstrakte Klasse kann Instanzmethoden haben, die ein Standardverhalten implementieren.

In einem Java-Interface deklarierte Variablen sind standardmäßig final. Eine abstrakte Klasse kann nicht-finale Variablen enthalten.

Elemente eines Java-Interfaces sind standardmäßig öffentlich. Eine Java-abstrakte Klasse kann die üblichen Arten von Klassenelementen wie private, geschützte usw. haben.

Ein Java-Interface sollte mit dem Schlüsselwort "implements" implementiert werden; Eine Java-abstrakte Klasse sollte mit dem Schlüsselwort "extends" erweitert werden.

Ein Interface kann nur ein anderes Java-Interface erweitern, eine abstrakte Klasse kann eine andere Java-Klasse erweitern und mehrere Java-Interfaces implementieren.

Eine Java-Klasse kann mehrere Interfaces implementieren, aber nur eine abstrakte Klasse erweitern.

Der Interviewer war jedoch nicht zufrieden und sagte mir, dass diese Beschreibung "buchhalterisches Wissen" widerspiegelte.

Er bat mich um eine praktischere Antwort und erklärte, wann ich eine abstrakte Klasse anstelle eines Interfaces wählen würde, unter Verwendung praktischer Beispiele.

Wo habe ich einen Fehler gemacht?

23voto

Sergiu Dumitriu Punkte 11050

Viele Junior-Entwickler machen den Fehler, Interfaces, abstrakte und konkrete Klassen als leichte Variationen derselben Sache zu betrachten und eine davon rein aus technischen Gründen zu wählen: Brauche ich Mehrfachvererbung? Brauche ich einen Ort für gemeinsame Methoden? Muss ich mich um etwas anderes kümmern als nur um eine konkrete Klasse? Das ist falsch, und in diesen Fragen verbirgt sich das Hauptproblem: "Ich". Wenn du Code nur für dich selbst schreibst, denkst du selten an andere gegenwärtige oder zukünftige Entwickler, die an deinem Code arbeiten oder mit ihm interagieren werden.

Interfaces und abstrakte Klassen haben trotz ihrer technischen Ähnlichkeit vollkommen unterschiedliche Bedeutungen und Zwecke.

Zusammenfassung

  1. Ein Interface definiert einen Vertrag, den irgendeine Implementierung für dich erfüllen wird.

  2. Eine abstrakte Klasse bietet ein Standardverhalten, das deine Implementierung wiederverwenden kann.

Die obigen beiden Punkte sind das, wonach ich bei Bewerbungsgesprächen suche, und sie sind eine kompakte Zusammenfassung. Lies weiter für mehr Details.

Alternative Zusammenfassung

  1. Ein Interface dient zum Definieren öffentlicher APIs
  2. Eine abstrakte Klasse dient für den internen Gebrauch und zum Definieren von SPIs

Am Beispiel erklärt

Um es anders auszudrücken: Eine konkrete Klasse erledigt die eigentliche Arbeit, auf eine sehr spezifische Weise. Zum Beispiel verwendet ein ArrayList einen zusammenhängenden Speicherbereich, um eine Liste von Objekten in kompakter Weise zu speichern, was schnellen zufälligen Zugriff, Iteration und Änderungen vor Ort bietet, aber schlecht bei Einfügungen, Löschungen und manchmal sogar bei Ergänzungen ist; währenddessen verwendet ein LinkedList doppelt verkettete Knoten, um eine Liste von Objekten zu speichern, was stattdessen schnelle Iteration, Änderungen vor Ort und Einfügung/Löschung/Ergänzung bietet, aber schlecht bei zufälligem Zugriff ist. Diese beiden Arten von Listen sind für verschiedene Anwendungsfälle optimiert, und es ist wichtig, wie du sie verwenden wirst. Wenn du Leistung aus einer Liste herausholen möchtest, mit der du stark interagierst, und die Wahl des Listentyps bei dir liegt, solltest du sorgfältig überlegen, welchen du instanziierst.

Andererseits interessieren sich hochrangige Nutzer einer Liste nicht wirklich dafür, wie sie tatsächlich implementiert ist, und sie sollten von diesen Details abgeschirmt werden. Stell dir vor, Java nicht das List Interface offengelegt hätte, sondern nur eine konkrete List Klasse hätte, die tatsächlich das ist, was LinkedList jetzt ist. Alle Java-Entwickler hätten ihren Code an die Implementierungsdetails angepasst: Vermeide zufälligen Zugriff, füge einen Cache hinzu, um den Zugriff zu beschleunigen, oder implementiere einfach ArrayList selbst, obwohl es mit dem gesamten anderen Code, der nur mit List funktioniert, inkompatibel wäre. Das wäre schrecklich... Aber stell dir jetzt vor, dass die Java-Meister tatsächlich erkennen, dass eine verkettete Liste für die meisten tatsächlichen Anwendungsfälle nicht geeignet ist, und beschließen, zu einer Array-List für ihre einzige verfügbare List Klasse zu wechseln. Dies würde die Leistung jedes Java-Programms auf der ganzen Welt beeinflussen, und die Leute wären nicht glücklich darüber. Und der Hauptübeltäter ist, dass Implementierungsdetails verfügbar waren, und die Entwickler davon ausgegangen sind, dass diese Details einen dauerhaften Vertrag darstellen, auf den sie sich verlassen können. Deshalb ist es wichtig, Implementierungsdetails zu verbergen und nur einen abstrakten Vertrag zu definieren. Das ist der Zweck eines Interfaces: Definiere, welche Art von Eingabe eine Methode akzeptiert und welche Art von Ausgabe erwartet wird, ohne alle internen Details preiszugeben, die Programmierer dazu verleiten könnten, ihren Code an die internen Details anzupassen, die sich bei jedem zukünftigen Update ändern könnten.

Eine abstrakte Klasse steht zwischen Interfaces und konkreten Klassen. Sie soll Implementierungen dabei unterstützen, gemeinsamen oder langweiligen Code zu teilen. Zum Beispiel bietet die AbstractCollection grundlegende Implementierungen für isEmpty, basierend auf der Größe 0, contains als Iteration und Vergleich, addAll als wiederholtes add usw. Dies ermöglicht es Implementierungen, sich auf die entscheidenden Teile zu konzentrieren, die sie voneinander unterscheiden: wie man Daten tatsächlich speichert und abruft.

Eine andere Perspektive: APIs gegenüber SPIs

Interfaces sind niedrig kohäsive Gateways zwischen verschiedenen Teilen des Codes. Sie ermöglichen es Bibliotheken zu existieren und sich weiterzuentwickeln, ohne jedes Mal alle Bibliotheksbenutzer zu brechen, wenn sich intern etwas ändert. Es heißt Application Programming Interface, nicht Application Programming Klassen. Auf kleinerer Ebene ermöglichen sie auch mehreren Entwicklern eine erfolgreiche Zusammenarbeit an groß angelegten Projekten, indem sie verschiedene Module durch gut dokumentierte Schnittstellen trennen.

Abstrakte Klassen sind hoch kohäsive Helfer, die verwendet werden, wenn eine Schnittstelle implementiert wird und einige Implementierungsdetails angenommen werden. Alternativ werden abstrakte Klassen zur Definition von SPIs, Service Provider Interfaces, verwendet.

Der Unterschied zwischen einer API und einem SPI ist subtil, aber wichtig: Bei einer API liegt der Fokus darauf, wer sie nutzt, und bei einem SPI liegt der Fokus darauf, wer sie implementiert.

Das Hinzufügen von Methoden zu einer API ist einfach, alle bestehenden Benutzer der API werden noch kompilieren. Das Hinzufügen von Methoden zu einem SPI ist schwierig, da jeder Dienstanbieter (konkrete Implementierung) die neuen Methoden implementieren muss. Wenn Interfaces verwendet werden, um ein SPI zu definieren, muss ein Anbieter eine neue Version veröffentlichen, wenn sich der SPI-Vertrag ändert. Werden stattdessen abstrakte Klassen verwendet, können neue Methoden entweder in Bezug auf bestehende abstrakte Methoden definiert werden oder als leere throw not implemented exception Stubs, die es zumindest erlauben, dass eine ältere Version einer Dienstimplementierung noch kompiliert und funktioniert.

Eine Anmerkung zu Java 8 und Standardmethoden

Obwohl Java 8 Standardmethoden für Interfaces eingeführt hat, was die Grenze zwischen Interfaces und abstrakten Klassen noch verschwommener macht, geschah dies nicht, damit Implementierungen Code wiederverwenden können, sondern um es einfacher zu machen, Schnittstellen zu ändern, die sowohl als API als auch als SPI dienen (oder fälschlicherweise für die Definition von SPIs anstelle von abstrakten Klassen verwendet werden).

"Buchwissen"

Die technischen Details, die in der Antwort des OP bereitgestellt werden, gelten als "Buchwissen", weil dies normalerweise der Ansatz ist, der in der Schule und in den meisten Technologiebüchern über eine Sprache verwendet wird: was eine Sache ist, nicht wie man sie in der Praxis verwendet, insbesondere in groß angelegten Anwendungen.

Hier ist eine Analogie: Angenommen, die Frage war:

Was ist besser für den Abschlussball zu mieten, ein Auto oder ein Hotelzimmer?

Die technische Antwort hört sich so an:

Nun, in einem Auto kannst du es schneller machen, aber in einem Hotelzimmer kannst du es bequemer machen. Auf der anderen Seite ist das Hotelzimmer nur an einem Ort, während du in einem Auto an mehreren Orten, wie zum Beispiel am Aussichtspunkt für eine schöne Aussicht, oder im Autokino, oder vielen anderen Orten, oder sogar an mehreren Orten, machen kannst. Außerdem hat das Hotelzimmer eine Dusche.

Das ist alles wahr, aber verfehlt vollkommen den Punkt, dass es sich um zwei völlig verschiedene Dinge handelt und beide gleichzeitig für unterschiedliche Zwecke verwendet werden können und der "den es zu machen" Aspekt nicht das Wichtigste an einer der beiden Optionen ist. Die Antwort fehlt der Perspektive, und zeigt eine unreife Denkweise, während sie korrekte "Fakten" präsentiert.

10voto

Niklas Rosencrantz Punkte 23722

Ein Interface ist ein "Vertrag", bei dem die Klasse, die den Vertrag implementiert, verspricht, die Methoden zu implementieren. Ein Beispiel, bei dem ich ein Interface anstelle einer Klasse schreiben musste, war, als ich ein Spiel von 2D auf 3D aktualisierte. Ich musste ein Interface erstellen, um Klassen zwischen der 2D- und der 3D-Version des Spiels zu teilen.

package adventure;
import java.awt.*;
public interface Playable {
    public void playSound(String s);
    public Image loadPicture(String s);    
}

Dann kann ich die Methoden basierend auf der Umgebung implementieren und dennoch in der Lage sein, diese Methoden von einem Objekt aus aufzurufen, das nicht weiß, welche Version des Spiels geladen wird.

public class Adventure extends JFrame implements Playable

public class Dungeon3D extends SimpleApplication implements Playable

public class Main extends SimpleApplication implements AnimEventListener, ActionListener, Playable

Typischerweise kann in der Spielwelt die Welt eine abstrakte Klasse sein, die Methoden im Spiel ausführt:

public abstract class World...

    public Playable owner;

    public Playable getOwner() {
        return owner;
    }

    public void setOwner(Playable owner) {
        this.owner = owner;
    }

9voto

akohout Punkte 1792

Was ist mit dem folgenden Gedanken:

  • Eine Beziehung zwischen einer Klasse und einer abstrakten Klasse ist vom Typ "ist-ein"
  • Eine Beziehung zwischen einer Klasse und einem Interface ist vom Typ "hat-ein"

Also, wenn Sie eine abstrakte Klasse Säugetiere, eine Unterklasse Mensch und ein Interface Fahren haben, dann können Sie sagen

  • jeder Mensch ist ein Säugetier
  • jeder Mensch hat Fahren (Verhalten)

Mein Vorschlag ist, dass die Buchwissensphrase darauf hindeutet, dass er den semantischen Unterschied zwischen beiden hören wollte (wie es andere hier bereits vorgeschlagen haben).

7voto

Sarfaraz Ahamad Punkte 71

Abstrakte Klassen sind keine reine Abstraktion, da es eine Sammlung von konkreten (implementierten Methoden) sowie nicht implementierten Methoden enthält. Aber Interfaces sind reine Abstraktionen, da es nur nicht implementierte Methoden und keine konkreten Methoden gibt.

Warum abstrakte Klassen?

  1. Wenn der Benutzer gemeinsame Funktionalitäten für alle Objekte schreiben möchte.
  2. Abstrakte Klassen sind die beste Wahl für eine spätere Neuentwicklung, um mehr Funktionalitäten hinzuzufügen, ohne den Endbenutzer zu beeinträchtigen.

Warum Interfaces?

  1. Wenn der Benutzer unterschiedliche Funktionalitäten schreiben möchte, die sich auf den Objekten unterscheiden.
  2. Interfaces sind die beste Wahl, wenn die Anforderungen nicht geändert werden müssen, sobald das Interface veröffentlicht wurde.

5voto

Der Hauptunterschied, den ich beobachtet habe, besteht darin, dass eine abstrakte Klasse uns bereits einige gemeinsame Verhaltensweisen bietet, die bereits implementiert sind und Unterklassen nur die spezifische Funktionalität implementieren müssen, die ihnen entspricht. Während eine Schnittstelle nur angeben wird, welche Aufgaben erledigt werden müssen, und keine Implementierungen von der Schnittstelle gegeben werden. Ich kann sagen, dass es den Vertrag zwischen sich selbst und den implementierten Klassen spezifiziert.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X