Schnittstellen (Interfaces)
Erbt eine Klasse von mehreren Klassen gleichzeitig, spricht man von Mehrfachvererbung. Dieses Konzept wird von vielen Programmiersprachen nicht direkt unterstützt, da es zu Mehrdeutigkeiten führen kann: Erbt eine Klasse über mehrere Pfade von derselben Basisklasse und wurde dabei eine Methode unterschiedlich überschrieben, entsteht Unklarheit, welche Implementierung verwendet werden soll. Aufgrund der Rautenform im Klassendiagramm bezeichnet man dieses Problem als Diamantenproblem.
Zur Lösung des Diamantenproblems werden Schnittstellen (Interfaces) eingesetzt. Schnittstellen sind im Wesentlichen abstrakte Klassen, die ausschließlich abstrakte Methoden enthalten. Sie stellen sicher, dass implementierende Klassen bestimmte Methoden bereitstellen, ohne eine konkrete Vererbungshierarchie vorzuschreiben.
Definition von Schnittstellen
Eine Schnittstelle wird ähnlich wie eine Klasse definiert, verwendet aber das
Schlüsselwort interface. Sie kann nur öffentliche abstrakte sowie öffentliche
statische Methoden enthalten.
public interface MobileDevice {
int getScreenSizeInInches();
}
Die Angabe von abstract und public bei Methoden ist nicht erforderlich.
Implementieren von Schnittstellen
Eine Schnittstelle wird mit dem Schlüsselwort implements von einer Klasse
implementiert. Damit verpflichtet sich die Klasse, alle Methoden der
Schnittstelle zu implementieren.
- Schnittstelle
- Implementierende Klasse
public interface MobileDevice {
int getScreenSizeInInches();
}
public class Notebook extends Computer implements MobileDevice {
...
@Override
public int getScreenSizeInInches() {
return screenSizeInInches;
}
...
}
Verwenden von Schnittstellen
Schnittstellen können wie Klassen als Datentypen verwendet werden. Die Typumwandlung von der implementierenden Klasse zur Schnittstelle bezeichnet man als Upcast, die Rückumwandlung als Downcast.
- Schnittstelle
- Implementierende Klasse A
- Implementierende Klasse B
- Startklasse
public interface MobileDevice {
int getScreenSizeInInches();
}
public class Notebook extends Computer implements MobileDevice {
...
@Override
public int getScreenSizeInInches() {
return screenSizeInInches;
}
@Override
public ArrayList<String> getSpecification() {
ArrayList<String> specification = new ArrayList<>();
specification.add("description: " + description);
specification.add("cpu: " + cpu);
specification.add("memoryInGb: " + memoryInGb);
specification.add("screenSizeInInches: " + screenSizeInInches);
return specification;
}
...
}
public class Smartphone implements MobileDevice {
...
private double widthInInches;
private double heightInInches;
@Override
public int getScreenSizeInInches() {
return (int) Math.sqrt(Math.pow(widthInInches, 2) + Math.pow(heightInInches, 2));
}
...
}
public class MainClass {
public static void main(String[] args) {
ArrayList<MobileDevice> mobileDevices = new ArrayList<>();
mobileDevices.add(new Notebook("Mein Gaming Laptop", new Cpu(4.7, 8), 32, 16)); // Upcast
mobileDevices.add(new Smartphone("Google Pixel 9", 2.8, 6)); // Upcast
for (MobileDevice m : mobileDevices) {
System.out.println(m.getScreenSizeInInches()); // Polymorphie
if (m instanceof Notebook n) { // Downcast
System.out.println(n.getSpecification());
}
}
}
}
Eine Schnittstelle eignet sich, wenn mehrere, voneinander unabhängige Klassen dasselbe Verhalten teilen sollen. Eine abstrakte Klasse eignet sich, wenn gemeinsame Implementierungen geerbt werden sollen.