11.1 Schnittstellen (Interfaces)

Javaklassen enthalten Methoden und Datenfelder. Die öffentlich zugänglichen Methoden und Datenfelder bilden die Schnittstellen einer Klasse. Schnittstellen sind wichtige Hilfsmittel bei Entwurf komplexer Softwaresysteme. Sie erlauben es unterschiedliche Subsysteme definiert zu koppeln (Coupling). Die Wechselwirkung zwischen diesen Subsystemen wird auch Coherence oder Cohesion genannt.

Schnittstellen unterstützen das Konzept des Information Hiding und sollten möglichst stabil sein. Stabile Schnittstellen erlauben es mehreren Teams unabhängig von einander zu arbeiten. Sie erlauben eine Trennung von

  • Spezifikation und
  • Implementierung

Schnittstellen (engl. Interfaces) sind ein wichtiges Sprachmittel beim Entwurf von Softwaresystemen.

Sie enthalten daher nur die für die Spezifikation wichtige Elemente:

  • Methodendeklarationen
  • öffentliche Attribute

Klassen hingegen enthalten die Spezifikation, alle Attribute sowie zusätzlich die Implementierung der Methoden durch die Methodenrümpfe.

Javaschnittstellen (Interfaces)

Java bietet im Gegensatz zu C++ das Schlüsselwort interface zur Beschreibung von Schnittstellen. Java-Interfaces muss man gedanklich als abstrakte Klassen verstehen, die selbst über keine Implementierung von Methoden verfügen.

Klassen enthalten die Spezifikation und zusätzlich die Implementierung der Methoden durch die Methoden Rümpfe/Blöcke. Schnittstellen in Java enthalten (in der Regel, siehe unten) nur die Spezifikation.

Eigenschaften Klasse abstrakte Klasse Schnittstelle
Öffentliche Attribute Ja Ja Ja
Private Attribute Ja Ja Nein
Methodenköpfe (die Spezifikation) Ja Ja Ja
Methodenrumpf Ja Nein (bei abstr. Meth.) Nein (in der Regel)
kann von Klasse erben Ja Ja Nein
kann von Schnittstelle erben Nein Nein Ja
kann eine oder mehrere Schnittstelle implementieren Ja Ja Nein

 Für Java-Schnittstellen gilt:

  • alle Attribute müssen öffentliche, finale und initialisierte Konstanten sein
  • alle Methoden sind abstrakt (abstract), öffentlich (public), nicht final und nicht statisch
  • sie geben keine Konstruktoren vor. Die Gründe sind:
    • Konstruktoren haben immer den Namen der aktuellen Klasse.
    • Ein Konstruktor in einer Implementierung kann nicht den Namen der implementierten Schnittstelle besitzen.

Notation einer Java-Schnittstelle

[public] interface Interface-name [extends Interface1, Interface2, ... ];
  • Das Schlüsselwort public erlaubt wie auch bei Klassen das Interface ausserhalb des zugehörigen Pakets zu benutzen
  • dem Schlüsselwort interface folgt der Name der Schnittstelle und
  • eine optionale Liste von Schnittstellen von denen die Schnittstelle erbt. Diese Liste von Schnittstellen wird mit dem Schlüsselwort extends eingeleitet

Schnittstellen können nur von Schnittstellen abgeleitet werden.

Klassen können nur von Klassen abgeleitet werden. Klassen implementieren jedoch beliebig viele Schnittstellen durch das Schlüsselwort implements.

[public|private|protected] class Klassenname1 extends Klassenname2 [implements Interface1, Interface2, ....]

Die Klasse Klassenname1 wird aus Klassenname2 abgeleitet und implementiert (Schlüsselwort implements) optional eines oder mehrere Interfaces.

Beispiel

Personen (class Person), sowie Lieferanten (class Supplier) implementieren das Interface BankAccount. BankAccount muss mit einer IBAN Nummer und dem Namen einer Bank umgehen können. Das Interface macht keinerlei Vorgaben zum Setzen dieses Daten. Das Erfassen dieser Daten obliegt dem Implementierer der Schnittstelle.

interface BankAccount {
   public long iban();
   public String bank();
}

class Person implements BankAccount {
   private String myIban;
   private String myBank;
   public name()  { // Implementierung
   }
   public long iban() {
      ...
      return myIban;
   }
   public String bank() {
      ...
      return myBank;
   }
}

class Supplier implements BankAccount {
   private String supplierIban;
   private String supplierBank;
   public String getLicense() { //Implementierung
      }
   public long iban() {
      ...
      return supplierIban;
   }
   public String bank() {
      return supplierBank;
   }
}
...
BankAccount b1 = new Person();
BankAccount b2 = new Supplier();
...
System.out.println(b1.bank());
System.out.println(b2.bank());

Die Klasse Person implementiert alle Methoden von BankAccount und verhält sich in allen Aspekten wie das Interface.

Für Klassen die Schnittstellen(Interfaces) implementieren gilt:

  • sie können beliebig viele Interfaces gleichzeitig implementieren.
  • sie implementieren alle Methoden und Attribute des Interface
    • implementieren sie nicht alle Aspekte des Interface müssen sie als abstrakte Klasse deklariert werden. Erst abgeleitete Klassen die alle Aspekte der Schnittstelle zur Verfügung stellen müssen nicht mehr abstrakt sein.

Schnittstellen und Vererbung

  • Schnittstellen können von Schnittstellen erben (Schlüsselwort extends)
    • Dies deutet für den Entwickler, dass er alle Methoden der Vererbungshierarchie implementieren muss. In diesen Aspekt verhalten sich Schnittstellenmethoden wie abstrakte Methoden.
  • Klassen können nicht von Schnittstellen erben. Sie implementieren Schnittstellen!

UML Notation

In UML Klassendiagrammen wird die Java Interface-beziehung als Verfeinerung mit gestrichelten Pfeilen gekennzeichnet: Alternativ kann man die zu implementierende Klasse auch mit einem Strich und einem Kreis darstellen:

UML Diagramm mit Java Schnittstellen

alternative Schnittstellenbeschreibung in UML

Anwendung von Schnittstellen (Interfaces)

Schnittstellen (Schlüsselwort interface) und Vererbung (Schlüsselwort extends) sind zwei Vererbungskonzepte in Java. Sie haben unterschiedliche Zielsetzungen bezüglich Strukturierung und Planung.

  • Schnittstellen (Interfaces)
    • fördern modularen Aufbau auf Softwaresystemen
      • erlauben unabhängige Implementierung und vereinfachen den Austausch von Schnittstellenimplementierungen
    • fordern nur die Implementierung bestimmter Methoden.
    • entkoppeln Systeme und fördern die genaue Spezifikation von benötigten Diensten (Verträge!)
    • schaffen Strukturen die in späteren Phasen der Softwareentwicklung die Komplexität der Implementierung senken
  • Vererbung
    • erleichert Codewiederverwendung
    • führt zu einer engeren Kopplung von Systemen.
      • Da immer eine Implementierung der Oberklasse genutzt werden muss. Man muss z.Bsp. den Konstruktor der Oberklasse immer nutzen
      • Eine Unterklasse muss immer alle Eigenschaften der Oberklasse aufweisen
  Vererbung (extends) Schnittstelle (interface)
Spezifikation wird von der Oberklasse vorgegeben wird vom Interface vorgegeben
Programmcode wird von der Oberklasse vorgegeben.
wird von einer abstrakten Oberklasse gefordert
Interface fordert eine Implementierung
Mehrfachvererbung/Implementierung nein ja
Abhängigkeit der Unterklasse/Interfaceimplementierung hängt stark von der Oberklasse ab hängt schwächer vom zu implementierenden Interface ab

Implikationen

Schnittstellen (Interfaces) müssen Aufgrund ihrer großen Bedeutung für den Entwurf von Softwaresystemen sehr sorgsam entworfen werfen. Änderungen in Schnittstellen führen dazu, dass alle Klassen die eine Schnittstelle  (Interface) implementieren, bei einer Änderung vom Laufzeitsystem nicht mehr akzeptiert werden. Bei einer Schnittstellenänderung müssen alle Klassen der neuen Schnittstelle angepasst und neu übersetzt werden. 

Schnittstellen versus Vererbung mit Polymorphismus

Schnittstellen sind oft eine bessere Lösung für langlebige Softwaresysteme. Die enge Kopplung an die vererbenden Basisklassen werden sich ändernden Anforderungen auch oft ein Hindernis. Der Vorteil alle ererbten Methoden direkt benutzen zu können, kann hier auch zum Nachteil werden, insbesondere wenn Klassenhierarchien tief und unübersichtlich sind.

Die Konzepte von

  • Assoziation und
  • Schnittstellen

führen oft zu einem initial höheren Implementierungsaufwand. Sie bieten jedoch eine bessere Entkopplung von Softwarekomponenten. 

Weiterführende Quellen: "Why extends is evil"

Genau hingeschaut: Methodenimplementierungen von Schnittstellen

Weiter oben wird behauptet, dass in der Deklaration einer Schnittstelle keine Implementierung von Methoden möglich sind. Diese Aussage ist seit Java 8 so nicht mehr korrekt. Sie gilt aber nach wie vor für sehr, sehr viele Anwendungsfälle.

Die neuen Möglichkeiten von "default" Methoden in Schnittstellen werden im Rahmen dieser Vorlesung nicht beachtet. Man sollte diesen Sonderfall kennen. Für das grundlegende Verständnis des Schnittstellenkonzepts ist dieser "exotische" Anwendungsfall aber nicht notwendig.

Hintergrund

Vor Java 8 waren alle Methoden eines Interfaces abstrakt. Das heißt, es lag nur der Methodenkopf mit der Signatur vor. Seit Java 8 kann man in Interfaces

  • default-Methoden implementieren
  • statische Methoden implementieren

Die Notwendigkeit dieser Konzepte entstanden durch die Integration von Lamdba-Ausdrücken (JSR 335) . Man benötigt diese Konzepte um die Rückwärtskompatibilität zu älteren Javaimplementierungen zu gewährleisten. Es beruht auf dem Problem der Java Interface-Evolution bei der Benutzung der Collection Klassen in Lamdba-Ausdrücken. Angelika Langer hat dieses Problem sehr gut in einem deutschsprachigen Internetartikel im Java Magazin beschrieben.