Zum Inhalt
wannacode

Interfaces

Kurz gesagt: Ein Interface ist ein Vertrag: eine Liste von Methoden ohne Rumpf, die jede Klasse schreiben muss, die es implementiert; eine Klasse kann mehrere Interfaces implementieren.

Problem

Gehälter und Rechnungen

Am Monatsende bezahlt die Firma zwei Arten von Dingen: die Gehälter der Mitarbeiter und die Rechnungen der Lieferanten. Du willst ein Array und eine Schleife: wer bezahlt wird, wie viel, und am Ende die Summe.

Ein Array Employee[] geht nicht: Eine Rechnung ist kein Mitarbeiter. Sie von Employee erben zu lassen ist falsch. Der Satz „eine Rechnung ist ein Mitarbeiter“ stimmt nicht, und die Rechnung bekäme auch einen Namen, ein Gehalt und eine Erhöhung.

Eine gemeinsame Basisklasse Payment geht auch nicht. Manager hat schon eine Basisklasse, Employee, und eine Klasse kann nur eine haben. Mitarbeiter und Rechnungen haben nichts gemeinsam, außer dass sie bezahlt werden können.

Die Idee

Ein Interface: ein Vertrag „kann etwas“

Ein Interface ist eine Liste von Methoden ohne Rumpf. Es sagt, was eine Klasse können muss, aber nicht wie:

interface Payable {
int amountDue();
String payee();
}
Eine Klasse, die den Vertrag annimmt, implementiert das Interface und schreibt jede seiner Methoden:
class Invoice implements Payable {
private String supplier;
private int amount;
public Invoice(String supplier, int amount) {
this.supplier = supplier;
this.amount = amount;
}
@Override
public int amountDue() {
return amount;
}
@Override
public String payee() {
return supplier;
}
}
Invoice power = new Invoice("Stromanbieter", 340);
System.out.println(power.payee() + ": " + power.amountDue() + " €");
Ausgabe
Stromanbieter: 340 €

Ein Interface implementierst du mit implements, nicht mit extends. Auch hier kannst du @Override schreiben: Der Compiler prüft, dass die Methode wirklich aus dem Interface kommt.

Die Methoden eines Interface sind immer public. Sie sind ein Versprechen an alle, also müssen sie auch in der Klasse public sein.

Sieh es dir an

Eine Schleife für alles, was bezahlt wird

Auch der Mitarbeiter nimmt den Vertrag an. Sein Betrag ist das Monatsgehalt:

class Employee implements Payable {
// ... name, salary, der Konstruktor, monthlyPay()
@Override
public int amountDue() {
return monthlyPay();
}
@Override
public String payee() {
return name;
}
}

Jetzt sind sowohl die Mitarbeiter als auch die Rechnungen Payable. Ein Interface ist ein Typ: Es kann der Typ einer Variable, eines Parameters oder eines Arrays sein.

Payable[] payments = {
new Employee("Ivan", 2000),
new Manager("Maria", 3000, 500),
new Invoice("Stromanbieter", 340),
new Invoice("Vermieter", 1800)
};
int total = 0;
for (Payable p : payments) {
System.out.println(p.payee() + ": " + p.amountDue() + " €");
total += p.amountDue();
}
System.out.println("Gesamt: " + total + " €");
Ausgabe
Ivan: 2000 €
Maria: 3500 €
Stromanbieter: 340 €
Vermieter: 1800 €
Gesamt: 7640 €

Der Manager schreibt nichts Neues. Von Employee erbt er sowohl den Vertrag als auch amountDue(). Und amountDue() ruft monthlyPay() auf, das der Manager überschrieben hat. Deshalb bekommt Maria 3500 €.

Payable[] payments = {
new Employee("Ivan", 2000),
new Manager("Maria", 3000, 500),
new Invoice("Stromanbieter", 340)
};
int total = 0;
for (Payable p : payments) {
total += p.amountDue();
}
System.out.println("Gesamt: " + total + " €");
Schritt für Schritt, in Worten
  1. Das Array ist vom Typ Payable. Die Pfeile zeigen auf drei Klassen, aber sie erreichen nur ihren Vertrag.
  2. total beginnt bei 0.
  3. p zeigt auf den Mitarbeiter: amountDue() gibt das Gehalt zurück, 2000.
  4. Der Manager ist ein Mitarbeiter, also kann auch er bezahlt werden. Gehalt plus Bonus: 3500.
  5. Die Rechnung ist kein Mitarbeiter, aber sie hält denselben Vertrag ein: 340.
  6. Die Schleife kennt die Klassen nicht. Sie weiß nur, dass jedes Objekt bezahlt werden kann.

Probier es aus

Wie hoch ist der Betrag?

Was gibt das Programm aus? Die Klassen sind aus der Karte oben.

Payable p = new Manager("Maria", 3000, 500);
System.out.println(p.amountDue());
Antwort

Die Idee

Eine Basisklasse, viele Verträge

Die Buchhaltung will die Rechnungen auch im CSV-Format: je eine Zeile „Lieferant;Betrag“. Das ist ein zweiter Vertrag. Eine Klasse kann beliebig viele Interfaces implementieren, durch Kommas getrennt:

interface Exportable {
String toCsv();
}
class Invoice implements Payable, Exportable {
// ... wie vorher
@Override
public String toCsv() {
return supplier + ";" + amount;
}
}
Exportable[] forAccounting = { new Invoice("Stromanbieter", 340), new Invoice("Vermieter", 1800) };
for (Exportable e : forAccounting) {
System.out.println(e.toCsv());
}
Ausgabe
Stromanbieter;340
Vermieter;1800

Hat die Klasse sowohl eine Basisklasse als auch Interfaces, kommt die Basisklasse zuerst:

class Manager extends Employee implements Exportable {

Es gibt eine Basisklasse und viele Verträge. So kann eine Klasse, deren Basisklasse schon vergeben ist, trotzdem mit ganz anderen Klassen in ein gemeinsames Array.

Probier es aus

Finde den Fehler

Diese Rechnung lässt sich nicht kompilieren. In welcher Zeile ist der Fehler?

Wähle die Zeile mit dem Fehler.

Probier es aus

Was fehlt?

class Invoice  Payable {
    // ...
}

Die Idee

Abstrakte Klasse oder Interface?

Beides sind Basisklassen, von denen man keine Objekte erzeugt, und beide legen den Klassen darunter Pflichten auf. Der Unterschied liegt darin, was sie sagen:

Abstrakte Klasse Interface
Sagt „X ist ein Y“ „X kann“
Wie viele pro Klasse eine beliebig viele
Felder und Konstruktor ja nein
Gemeinsamer Code ja kaum
  • Eine abstrakte Klasse, wenn die Klassen darunter eine Art von Ding sind und Code und Daten teilen: Figuren haben einen Namen und eine Beschreibung, Mitarbeiter einen Namen und ein Gehalt.
  • Ein Interface, wenn die Klassen verschieden sind, aber eine gemeinsame Fähigkeit brauchen: Ein Mitarbeiter und eine Rechnung „können bezahlt werden“, ein Vogel und ein Flugzeug „können fliegen“.
Oft werden sie zusammen benutzt: Employee ist die Basisklasse von Manager und Praktikant und implementiert gleichzeitig Payable.
Genauer, für Neugierige
  • Seit Java 8 kann ein Interface eine Standardmethode (default) haben, mit Rumpf. Die Klassen bekommen sie fertig und können sie auch überschreiben. So kannst du einem Interface eine neue Methode hinzufügen, ohne die Klassen kaputt zu machen, die es schon implementieren:
interface Flyable {
void fly();
default void glide() {
System.out.println("Gleitet durch die Luft");
}
}
class Bird implements Flyable {
@Override
public void fly() {
System.out.println("Der Vogel schlägt mit den Flügeln");
}
}
class Plane implements Flyable {
@Override
public void fly() {
System.out.println("Das Flugzeug hebt ab");
}
}
Flyable[] flyers = { new Bird(), new Plane() };
for (Flyable f : flyers) {
f.fly();
f.glide();
}
Ausgabe
Der Vogel schlägt mit den Flügeln
Gleitet durch die Luft
Das Flugzeug hebt ab
Gleitet durch die Luft
  • Ein Interface kann keine Objektfelder haben. Konstanten schon: Jedes Feld darin ist public static final.

Übung

Verträge

Löse die Aufgaben in deinem Editor.

1. Hupe. Ein Interface Honkable mit einer Methode honk(). SimpleCar aus dem Modul „Klassen und Objekte“ implementiert es.

Ausgabe
Tut-tut!

2. Angaben. Ein Interface Printable mit einer Methode printDetails(). Implementiere es in Person, und lass Student (eine Unterklasse von Person) es überschreiben und den Notendurchschnitt hinzufügen. Leg beide in ein Array Printable[].

Ausgabe
Name: Georg, Alter: 45
Name: Elena, Alter: 19
Notendurchschnitt: 5.5

3. Tanz. Ein Interface Danceable mit einer Methode dance(). DanceMove (ein Name und eine Dauer) implementiert es.

Ausgabe
Ich tanze Salsa für 30 Sekunden

4. DJ. Eine Klasse Song mit einem Titel. Ein Interface Playable mit einer Methode play(). DJ implementiert es und hat eine Methode playSong(Song song), die sich das Lied merkt und es abspielt. Gibt es noch kein Lied, sagt play() das.

Ausgabe
Kein Lied ausgewählt
Ich spiele 'Bohemian Rhapsody'

5. Der Teich. Interfaces Swimmable mit swim() und Flyable mit fly(). Fish schwimmt. Duck schwimmt und fliegt.

Ausgabe
Der Fisch schwimmt.
Die Ente schwimmt.
Die Ente fliegt.

Weitere Aufgaben

  • Rabatt. Ein Interface Discountable mit einer Methode, die den Preis nach dem Rabatt zurückgibt. Implementiere es in Product (10% Rabatt) und in Subscription (25% Rabatt für Studierende). Berechne die Summe eines Warenkorbs mit einem Produkt für 50 € und einem Studentenabo für 12 €: 45 € + 9 € = 54 €.
  • CSV für alle. Lass auch Employee Exportable implementieren, mit einer Zeile „Name;Monatsgehalt“. Leg Mitarbeiter und Rechnungen in ein Array Exportable[].

Test

Teste dich

1. Wie viele Interfaces kann eine Klasse implementieren?
2. Lässt sich new Payable() kompilieren?
3. Eine Klasse implementiert eine Methode des Interface ohne public. Was passiert?
4. Wie sagst du in Java, dass eine Klasse ein Interface implementiert?
5. Mitarbeiter und Rechnungen sollen in einer Schleife bezahlt werden. Sie haben keinen gemeinsamen Code. Was passt besser?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·