Interfaces¶
Interfaces sind auch abstrakte Klassen. Interfaces enthalten ausschließlich abstrakte Methoden (keine Methode darf implementiert sein). Interfaces beschreiben Schnittstellen. Für Interfaces wird nicht das Schlüsselwort class
, sondern interface
verwendet. Klassen erben nicht von Interfaces, sondern implementieren sie. Deshalb wird auch nicht das Schlüsselwort extends
, sondern das Schlüsselwort implements
verwendet. Während in Java nur von genau einer Klasse geerbt werden kann (also auch nur von genau einer abstrakten Klasse), kann eine Klasse beliebig viele Interfaces implementieren.
Interfaces sind automatisch abstract
, d.h. das Schlüsselwort abstract
muss nicht angegeben werden. Auch die Methoden in Interfaces müssen nicht als abstrakt gekennzeichnet werden. Interfaces können, wie abstrakte Klassen auch, als Typen verwendet werden.
Abtrakte Klasse | Interface |
---|---|
können abstrakte und nicht-abstrakte (also implementierte) Methoden haben | können nur abstrakte Methoden beinhalten |
es kann nur von einer (abstrakten) Klasse geerbt werden (Schlüsselwort extends ) |
es können beliebig viele Interfaces implementiert werden (Schlüsselwort implements ), mehrere Interfaces durch Komma getrennt |
abstrakte Klassen können selbst Interfaces implementieren | Interfaces können keine abstrakten Klassen implementieren (alle Methoden müssen ja abstrakt sein) |
das Schlüsselwort abstract deklariert eine abstrakte Klasse (und eine abstrakte Methode) |
das Schlüsselwort interface deklariert ein Interface |
eine abstrakte Klasse kann von einer anderen abstrakten Klasse erben und mehrere Interfaces implementieren | ein Interface kann nur von einem anderen Interface erben |
abtrakte Klassen können final Variablen (Konstanten), nicht-finale Variablen, statische und nicht-statische Variablen als Eigenschaften beinhalten |
Interfaces können nur statische Konstanten (static final ) als Eigenschaften beinhalten |
die Eigenschaften einer abstrakten Klasse können private , protected , default und public sein |
in Interfaces sind alle Eigenschaften public |
Bsp.: public abstract class Shape{ public abstract void draw(); } |
Bsp.: public interface Drawable{ void draw(); } |
Das Interface Comparable
¶
Ehe wir uns ein eigenes Interface schreiben, schauen wir uns zunächst die Verwendung eines bereits existierenden Interfaces an. Es handelt sich um das Interface Comparable aus dem java.lang
-Paket. Wenn Sie sich die Java-Dokumentation dieses Interfaces einmal anschauen, dann sehen Sie, dass es von sehr vielen Klassen implementiert wird. Dieses Interface enthält genau eine (natürlich abstrakte) Methode compareTo()
. Diese Methode kennen wir auch schon, denn wir haben sie betrachtet, als wir in Prog1 Strings kennengelernt haben.
Die Methode this.compareTo(Object obj)
wird verwendet, um zu vergleichen, ob this
größer, kleiner oder gleich obj
ist. Das bedeutet, dass wir compareTo()
in unserer Klasse implementieren sollten, wenn wir die Objekte unserer Klasse der Größe nach ordnen wollen, wenn wir also ermöglichen wollen, dass die Objekte der Klasse sortiert werden können.
Die Methode this.compareTo(Object obj)
gibt ein int
zurück, für dessen Wert Folgendes gelten soll:
- ist der zurückgegebene
int
-Wert positiv (> 0
), dann istthis
größer alsobj
, - ist der zurückgegebene
int
-Wert negativ (< 0
), dann istthis
kleiner alsobj
, - ist der zurückgegebene
int
-Wert0
, dann istthis
gleichobj
.
Angenommen, wir wollen für die folgende Klasse Rectangle
(aus dem Abschnitt Abstrakte Klassen) festlegen, dass die Rechtecke der Größe nach geordnet werden können. Gegeben ist also zunächst folgende Klasse (wir verwenden hier auch Shape
aus Abstrakte Klassen):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Die Klasse Rectangle
erbt also von der abstrakten Klasse Shape
und muss deshalb die Methoden perimeter()
und area()
implementieren. Nun geben wir an, dass Rectangle
auch das Interface Comparable
implementieren soll. Dazu ergänzen wir die erste Zeile um implements Comparable
, d.h. die Klassendeklaration sieht jetzt so aus:
1 2 |
|
Wenn Sie das hinzufügen, stellen wir fest, dass ein Fehler erzeugt wird (die Klasse lässt sich nicht compilieren). Die Fehlerausgabe besagt: The type Rectangle must implement the inherited abstract method Comparable.compareTo(Object)
. Es werden zwei QuickFixes
angeboten,
- entweder
Add unimplemented methods
- oder
Make type Rectangle abstract
.
Letzteres wollen wir aber nicht (Rectangle
soll nicht zu einer abstrakten Klasse gemacht werden). Also wählen wir Add unimplemented methods
. Eclipse fügt uns die compareTo()
-Methode in den Code ein:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Jetzt lässt sich der Code bereits compilieren, wir erhalten aber noch eine Warnung:
Comparable is a raw type. References to generic type Comparable<T> should be parameterized
Diese Warnung besagt, dass wir, wie wir das von Collections bereits kennen, auch das Interface Comparable
typisieren sollen. Das wollen wir auch tun, denn wir implementieren dieses Interface hier für unsere Klasse Rectangle
. Wir typisieren deshalb Comparable
mit Rectangle
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Interssanterweise ist nun zwar unsere Warnung weg, aber dafür erhalten wir erneut einen Fehler:
The type Rectangle must implement the inherited abstract method Comparable<Rectangle>.compareTo(Rectangle)
Dadurch, dass wir Comparable
mit Rectangle
typisieren (was korrekt ist), wird nun verlangt, dass wir nicht mehr die Methode
@Override
public int compareTo(Object o) {
// TODO Auto-generated method stub
return 0;
}
implementieren, sondern die Methode
@Override
public int compareTo(Rectangle o) {
// TODO Auto-generated method stub
return 0;
}
Der Typ des Parameters hat sich durch unsere Typisierung also geändert. Das ist gut, denn dann müssen wir nicht mehr, wie z.B. bei equals(Object o)
, prüfen, ob es sich bei dem übergebenen Objekt tatsächlich um ein Rectangle
handelt. Wir ändern also den Parametertyp in compareTo()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
In Zukunft typisieren wir das Comparable
-Interface noch, bevor wir Add unimplemented methods
wählen. Wir typisieren es stets mit der Klasse, in der wir das Interface implementieren.
Für die Implementierung müssen wir uns nun überlegen, wann ein Rectangle
-Objekt größer (kleiner/gleich) sein soll, als ein anderes. Da compareTo()
ein int
zurückgibt, könnten wir z.B. die Summen von height
und width
verwenden:
23 24 25 26 27 |
|
Wenn die Summe von height
und width
von this
größer ist, als von o
, dann geben wir eine positive int
-Zahl zurück, wenn sie kleiner ist, dann eine negative int
-Zahl und wenn sie gleich sind, dann 0
. Damit entsprechen wir den Vorgaben von compareTo()
.
Laufzeittypen eines Rectangle
-Objektes¶
Ein Rectangle
-Objekt ist nicht nur vom Laufzeittyp Rectangle
, sondern auch
- von Laufzeittyp
Shape
, wegenpublic class Rectangle extends Shape
, - vom Laufzeittyp
Comparable
, wegenpublic class Rectangle implements Comparable
und - vom Laufzeittyp
Object
, weil das immer so ist, weil jede Klasse implizit vonObject
erbt.
Wir könnten nun also in jeder beliebigen Klasse eine Sortiermethode haben, z.B.:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Die Methode implementiert Bubble-Sort. In Zeile 7
verwenden wir die compareTo()
-Methode. Das geht genau deshalb, weil klar ist, dass ein Objekt, das (auch) vom Typ Comparable
ist, diese Methode auf jeden Fall als Eigenschaft besitzt. Wenn wir nun in der Klasse, in der die Methode sortieren()
implementiert ist, folgende main()
-Methode haben:
public static void main(String[] args) {
Rectangle[] rectArr = new Rectangle[6];
rectArr[0] = new Rectangle(9, 13);
rectArr[1] = new Rectangle(4, 17);
rectArr[2] = new Rectangle(12, 5);
rectArr[3] = new Rectangle(8, 9);
rectArr[4] = new Rectangle(10, 11);
rectArr[5] = new Rectangle(5, 15);
System.out.printf("%n%n------------------------ unsortiert --------------------------%n%n");
for(Rectangle r : rectArr)
{
System.out.println(r.toString());
}
System.out.printf("%n%n------------------------- sortiert ---------------------------%n%n");
sortieren(rectArr);
for(Rectangle r : rectArr)
{
System.out.println(r.toString());
}
}
dann erhalten wir folgende Ausgabe:
------------------------ unsortiert --------------------------
[ 9 x 13 = 117,00 ]
[ 4 x 17 = 68,00 ]
[ 12 x 5 = 60,00 ]
[ 8 x 9 = 72,00 ]
[ 10 x 11 = 110,00 ]
[ 5 x 15 = 75,00 ]
------------------------- sortiert ---------------------------
[ 12 x 5 = 60,00 ]
[ 8 x 9 = 72,00 ]
[ 5 x 15 = 75,00 ]
[ 4 x 17 = 68,00 ]
[ 10 x 11 = 110,00 ]
[ 9 x 13 = 117,00 ]
für den Fall, dass wir in unserer Klasse Rectangle
auch die toString()
-Methode wie folgt implementiert haben:
@Override
public String toString()
{ String s = String.format("[ %2d x %2d = %6.2f ] ", this.width, this.height, this.area());
return s;
}
Success
Wir haben für unsere Klasse Rectangle
das Interface Comparable
implementiert. Das bedeutet, dass wir in Rectangle
die Methode compareTo()
so implementiert haben, dass Rectangle
-Objekte der Größe nach sortiert werden können. Wir haben also eine Ordnung über Rectangle
-Objekte definiert. Nach "außen" ist sichtbar, dass wir eine solche Ordnung implementiert haben, dass Rectangle
-Objekte also sortierbar sind, weil sie (auch) vom Typ Comparable
sind. Für alle Objekte, die in Java existieren, wissen wir, dass sie sortierbar sind, sobald sie auch vom Typ Comparable
sind. Comparable
stellt also eine Schnittstelle zur Sortierbarkeit dar. Wenn wir eine eigene Klasse schreiben und wir eine Ordnung über die Objekte dieser Klasse definieren können, sollten wir das Interface Comparable
implementieren, denn dadurch geben wir nach "außen" an, dass sich die Objekte der Klasse sortieren (ordnen) lassen.
Zwischenfazit¶
Wir haben nun schon mehrere Methoden kennengelernt, die wir für eigene Klassen implementieren sollten.
- Die
toString()
-Methode erben wir vonObjects
. Wir solltentoString()
für "unsere" Klassen überschreiben, damit wir eine textuelle Repräsentation unserer Objekte haben.toString()
wird implizit angewendet, sobald eineString
-Repräsentation erforderlich ist, z.B. istSystem.out.println(refVariable);
das Gleiche wieSystem.out.println(refVariable.toString());
. - Die
equals()
-Methode erben wir ebenfalls vonObjects
. Wir solltenequals()
für "unsere" Klassen implementieren, um zu definieren, wann Objekte "unserer" Klasse gleich sind. Hierbei ist wichtig, zu beachten, dassrefVar1 == refVar2
ein reiner Referenzvergleich ist, der nichts darüber aussagt, ob die Objekte gleich sind, sondern nur eintrue
ergibt, wenn beide Variablen auf dasselbe Objekt zeigen. Die Gleichheit von Objekten wird mittelsequals()
-Methode definiert. - Die
hashCode()
-Methode erben wir ebenfalls vonObjects
. Wir solltenhashCode()
genau dann implementieren, wenn wirequals()
implementieren. Wichtig ist, dass zwei Objekte den gleichen Hash-Code haben (hashCode()
liefert den gleichenint
-Wert zurück), wenn die beiden Objekte lautequals()
gleich sind. Gut ist darüber hinaus (aber nicht Bedingung), dass zwei Objekte einen unterschiedlichen Hash-Code haben, wenn sie lautequals()
-Methode nicht gleich sind (equals()
liefertfalse
zurück). Der Hash-Code wird bei Hash-basierten Datentypen, wie z.B. Collections verwendet, um diese einzusortieren. - Die Methode
compareTo()
muss implementiert werden, wenn wir das InterfaceComparable
implementieren. Mithilfe voncompareTo()
legen wir eine Ordnung über die Objekte der Klasse fest, d.h. wir geben an, wann ein Objekt größer/kleiner/gleich einem anderen Objekt der gleichen Klasse ist. Dadurch, dass wir dasComparable
-Interface implementieren, zeigen wir nach "außen", dass die Objekte unserer Klasse sortierbar sind.
Eine bessere Implementierung¶
Wir haben bereits bei der Implementierung der Klasse Rectangle
gesehen, dass wir das Interface Comparable
bei der Implementierung von Rectangle
typisieren sollten. Das wäre für eine wirklich korrekte Implementierung der Methode sortieren()
ebenfalls angebracht. Dann würden wir in dieser Methode Comparable
mit Rectangle
typisieren:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Wenn wir also den Typ Comparable
verwenden, dann ergänzen wir ihn um die Typisierung <Rectangle>
(Zeilen 1
und 9
). Das führt allerdings dazu, dass wir dann auch in Zeile 7
den Typ von unsorted[index+1]
nach Rectangle
konvertieren müssen ((Rectangle) unsorted[index+1]
). Damit verlieren wir aber unsere allgemeine Anwendbarkeit der Methode sortieren()
für alle Klassen, die Comparable
implementiert haben. Insbesondere würde die Methode dann nicht mehr für z.B. die Klasse Circle
anwendbar sein:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
Wenn wir nun versuchen würden, die sortieren()
-Methode auf ein Circle[]
anzuwenden, ließe sich das Programm gar nicht compilieren:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Deshalb wäre es eine bessere Implementierung, wenn wir das Interface Comparable
nicht in den konkreten Klassen Rectangle
und Circle
(und in jeder weiteren Klasse, die wir auf der Basis von Shape
erstellen) implementieren, sondern gleich in der Abstrakten Klasse Shape
:
public abstract class Shape implements Comparable<Shape>
{
public abstract double perimeter();
public abstract double area();
}
Da Shape
eine abstrakte Klasse ist, muss die Methode compareTo()
nicht in Shape
implementiert werden. Diese Methode würde nun abstract
an alle Klassen vererbt, die von Shape
erben:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
Beachten Sie, dass die Klassen Rectangle
und Circle
jetzt nur noch von Shape
erben, aber nicht mehr das Interface Comparable
implementieren (jeweils Zeile 1
). Es darf nicht mehrmals von einer Klasse implementiert werden und Shape
implementiert es ja bereits.
Da Shape
diese Interface aber implementiert, wird die Methode compareTo()
als abstrakte Methode an die Klassen Rectangle
und Circle
vererbt. Die Methode muss also von diesen Klassen implementiert werden. Nun wird sie aber mit dem Parametertyp Shape
vererbt (Zeile 24
in Rectangle.java
bzw. 23
in Circle.java
). Dieser Parameter muss deshalb zunächst innerhalb der Methode compareTo()
konvertiert werden (Zeile 25
in Circle.java
bzw. 26
in Rectangle.java
).
Die allgemeine Anwendung der Methode sortieren()
in der Testklasse gelingt nun aber:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
|
Wir können nun alle Objekte sortieren lassen, die auf der Klasse Shape
basieren.
------------------------ unsortiert --------------------------
[ 9 x 13 = 117,00 ]
[ 4 x 17 = 68,00 ]
[ 12 x 5 = 60,00 ]
[ 8 x 9 = 72,00 ]
[ 10 x 11 = 110,00 ]
[ 5 x 15 = 75,00 ]
------------------------- sortiert ---------------------------
[ 12 x 5 = 60,00 ]
[ 8 x 9 = 72,00 ]
[ 5 x 15 = 75,00 ]
[ 4 x 17 = 68,00 ]
[ 10 x 11 = 110,00 ]
[ 9 x 13 = 117,00 ]
------------------------ unsortiert --------------------------
(radius: 5,00 -> area: 78,54 ]
(radius: 5,50 -> area: 95,03 ]
(radius: 4,00 -> area: 50,27 ]
(radius: 2,50 -> area: 19,63 ]
(radius: 7,00 -> area: 153,94 ]
(radius: 1,00 -> area: 3,14 ]
------------------------- sortiert ---------------------------
(radius: 1,00 -> area: 3,14 ]
(radius: 2,50 -> area: 19,63 ]
(radius: 4,00 -> area: 50,27 ]
(radius: 5,00 -> area: 78,54 ]
(radius: 5,50 -> area: 95,03 ]
(radius: 7,00 -> area: 153,94 ]
Eine noch bessere Implementierung¶
Obwohl wir nun in Shape
das Interface Comparable
implementieren, geben wir die Verantwortung der Implementierung der Methode compareTo()
an die konkreten Klassen Rectangle
und Circle
weiter. Es stellt sich die Frage, ob sich die compareTo()
-Methode nicht bereits in Shape
implementieren ließe. Die Antwort auf diese Frage sollte ja lauten, denn ansonsten sollten wir das Interface gar nicht bereits durch die abstrakte Klasse Shape
implementieren lassen. Wir haben in Shape
genügend Informationen, um die compareTo()
-Methode zu implementieren. Wir können dafür entweder perimeter()
oder area()
verwenden. Wir entscheiden uns für die Verwendung von area()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
In abstrakten Klassen müssen nicht, im Gegensatz zu Interfaces, alle Methoden abstrakt sein. Es können auch Methoden bereits implementiert werden. Diese Methoden müssen dann nicht mehr in den Klassen implementiert werden, die von der abstrakten Klasse erben. Die Klassen Rectangle
und Circle
benötigen also keine eigene Implementierung der compareTo()
-Methode mehr:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Wir haben ausgenutzt, dass in der Klasse Shape
bereits genügend Informationen vorliegen, um die Methode compareTo()
korrekt für alle Klassen zu implementieren, die von Shape
erben. Diese Methode muss dann von diesen konkreten Klassen nicht mehr implementiert werden. Wir vermeiden so doppelten Code. Die testklasseShape
bleibt unverändert für alle abgeleiteten Klassen aus Shape
anwendbar.