Wirtschaftsinformatik (Bachelor-Studiengang): Programmierung (2. Semester)
Sie sind hier: Startseite › Wirtschaftsinformatik › Programmierung Java (Kurs 2): Abstrakte Klassen
HB / CM, Kurs vom 01.10.2002 - 31.03.2003
Abstrakte Klassen
Abstrakte Klassen sind Klassen, zu denen keine Objekte erzeugt werden können (im Gegensatz zu konkreten Klassen). Sie dienen häufig als Basisklassen von Klassenhierarchien. In abstrakten Klassen wird das Gemeinsame der abgeleiteten Klassen zusammengefasst.
Beim Entwurf von Klassenhierarchien kommt man durch Generalisierung zu abstrakten Klassen.
Warum abstrakt, d.h. keine Instanzen?
Häufig können Methoden erst für Spezialisierungen sinnvoll gestaltet werden.
Vorgelegte Folie: Frosch sagt "Ich kann nicht sprechen" ==> falsche Aussage
Welche Alternativen gibt es?
sprich
aus der KlasseTier
entfernensprich
inTier
belassen, aber keine Anweisung schreiben
Beide Lösungen sind schlecht.
ad1
: Folgendes Konstrukt ist dann
nicht mehr möglich:
...
Tier[ ] Kreatur = (new Tier("Frosch");
new Hund("Hund", "Lumpi", "Dackel");
new Tier("Ameise");
...
for(int i=0; i<Kreatur.length; i++)
Kreatur[i].sprich(); // Fehler! i=0, i=2; sprich existiert nicht
ad2
: Geht nur für Methoden
ohne Rückgabewert. Abgeleitete Klassen können
sprich
überschreiben,
müssen aber nicht.
Lösung:
sprich
wird als abstrakt deklariert.
Hinweis: Die Klasse Tier
wird zur
abstrakten Klasse!
Es können keine Instanzen von Tier
erzeugt werden. Für Frösche usw. müssen eigene Klassen abgeleitet
werden. In diesen Klassen muss sprich
überschrieben werden.
Und:
Tier
-Arrays sind möglich.
Wie werden abstrakte Klassen deklariert?
public abstract class A
{
...
}
Wann muss man Klassen als abstrakt deklarieren?
Wenn sie mindestens eine abstrakte Methode enthalten.
Nebenbemerkung:
Man kann Klassen auch als abstrakt deklarieren, wenn keine abstrakte Methode da ist, macht aber wenig Sinn.
Wie werden abstrakte Metoden deklariert?
public abstract int machWas();
Es gibt 3 Situationen, in denen eine Klasse C mindestens eine abstrakte Methode enthält (und aber selbst abstrakt sein muss):
- Mindestens eine der eigenen Methoden ist
abstract
. - C hat von einer Klasse A eine abstrakte Methode geerbt, aber nicht überschrieben.
- C hat ein Interface B übernommen, aber mindestens eine der Methoden von B nicht überschrieben.
» Wenn B von A erbt, müssen alle abstrakten Methoden von A in B überschrieben werden, damit B konkret werden kann!
Selbstverständlich können abstrakte Klassen auch gewöhnliche Methoden und Attribute haben.
Tier Kreatur;
...
Kreatur = new Frosch(...);
Interface (Schnittstellen)
Mit Hilfe von Interfaces kann - ähnlich wie mit abstrakten Klassen - sichergestellt werden, dass ein gewisses Verhalten, das in den Methoden des Interfaces festgelegt ist, in allen Klassen vorhanden ist, die das Interface übernehmen.
Die Klasse Arrays des Package
java.util
enthält u.a. folgende Methode zum Sortieren
von Referenzdatentypen-Arrays:
public static void sort(Object[ ] feld, int von, int bis)
Im Prinzip könnten hiermit alle Arrays der Form A[ ]
(A = Referenzdatentyp) sortiert werden.
Andererseits stellt sich die Frage, wonach sort A[ ]
sortieren soll?
Damit A-Objekte sortiert werden können, müssen je zwei von ihnen bezüglich ihrer "Größe" vergleichbar sein.
x < y
x == y
x > y
Wie macht man eine beliebige Klasse A "sortierbar"?
Mann muss erzwingen, dass je zwei A-Objekte vergleichbar sind.
Hinweis: Genau dazu dienen Interfaces!
hier:
Im Package java.lang
gibt
es ein Interface
Comparable
das genau eine
(abstrakte) Methode hat:
public int compareTo(Object y)
Jede Klasse, die das Interface Comparable
übernimmt, muss compareTo
sinnvoll überschreiben.
Hier:
x.compareTo(y) == -1 falls x<y
x.compareTo(y) == 0 falls x==y
x.compareTo(y) == +1 falls x>y
A wird somit sortierbar dadurch, dass
- das Interface
Comparable
übernommen wird - die Methode
compareTo
dieses Interfaces sinnvoll überschrieben wird.
Warum nicht - statt eines neuen Konzeptes "Interfaces" - einfach von einer abstrakten Klasse erben?
public abstract class Sortierbar
{
public abstract int compareTo(Object y);
}
public Class A extends Sortierbar
{
// compareTo überschreiben!
}
Da es in Java keine Mehrfacherbung gibt, könnten dann sortierbare Klassen von keiner anderen Klasse erben.
Schwachsinn!!!
Interfaces ersetzen in Java die Mehrfacherbung anderer objektorientierter Sprachen (z.B. C++).
Klassen in Java können nur von einer Klasse direkt erben, sie können aber beliebig viele Interfaces übernehmen.
Anwendung von Interfaces
Interfaces werden mit Hilfe
des Schlüsselwortes implements
übernommen.
public class B extends A implements S, T
Deklaration von Interfaces: ähnlich wie Klassen.
public interface S // überall benutzbar
{
...
}
interface T // nur im selben Package benutzbar
{
...
}
Andere Modifizierer sind nicht zulässig. Ausnahmen:
stricfp
,abstract
(überflüssig, weil alle Interfaces stetsabstract
sind)
Innerhalb Interfaces können vier Dinge deklariert werden:
- konstante Attribute
- abstrakte Methoden
- (geschachtelte) Klassen
- (geschachtelte) Interfaces
public interface Farbig
{
int ROT = 0;
int GELB = 1; // konstante Attribute
int GRUEN = 2;
int BLAU = 3;
void setzeFarbe(int dieFarbe); // abstrakte Methoden
int gibFarbe();
}
Die konstanten Attribute sind immer public static
final
.
- Es ist zulässig und üblich, diese Modifizierer wegzulassen.
- Die konstanten Attribute müssen immer initialisiert werden.
- Es ist üblich, sie vollständig groß zu schreiben.
- Manche Interfaces bestehen nur aus Konstanten.
Die Methoden eines Interfaces
sind immer public abstract
.
- Daher ist es auch hier zulässig und üblich, diese Modifizierer wegzulassen.
- Andere Modifizierer sind nicht erlaubt.
- Beim Überschreiben von Interfaces in Klassen können andere Modifizierer eingesetzt werden.
Interfaces können von
anderen Interfaces erben mittels
extends
. Anders als bei Klassen ist die
Mehrfacherbung zulässig.
public interface T extends Comparable, S
{
// Interfaces
}
Wie bei Klassen können die konstanten Attribute und die
abstrakten Methoden von Interfaces
beim Erben - bewusst oder unbewusst - überschrieben werden.
Daraus resultierende Konflikte werden für Attribute von
Interfaces genauso gelöst
wie für static
-Attribute von
Klassen.
public interface S
{
int WERT = -1;
}
public interface T extends S
{
int WERT = 1; // -1 wird überschrieben,
bzw. -1 wird durch 1 verborgen
}
Durch Voranstellen des Interface-Namens können aber beide überschrieben werden.
public class A implements T
{
public void schreibeWerte()
{
System.out.println("Wert: " + WERT); // 1
System.out.println("Wert S: " + S.WERT); // -1
System.out.println("Wert T: " + T.WERT); // 1
}
}
public class B
{
public void schreibeWert( )
{
System.out.println("Wert: " + A.WERT); // 1 == T.WERT, aber auf S.WERT so kein Zugriff
}
}
public class C implements S, T
{
public void schreibeWert( )
{
System.out.println("Wert: " + WERT); // welchen Wert? S.WERT oder T.WERT
} ==> Fehler!
}
Bei der Vererbung von Interfaces kann es bei Methoden kaum zu Konflikten kommen, da sie erst in den Klassen, die die Interfaces implementieren, realisiert werden. Für Methoden gleicher Signatur aus verschiedenen Interfaces kann es dabei immer nur eine Implementierung geben. Stimmen zwar die Namen, aber nicht die Signaturen überein, handelt es sich um das zulässige Überladen von Methoden. Schwierigkeiten kann es lediglich mit der Semantik von Methoden geben.
public interface Fahrbar
{
...
void bewege( ); // beschreibt das Bewegen von Autos
}
public interface Schiffbar
{
...
void bewege( ); // beschreibt das Bewegen von Schiffen
}
public class Transport implements Fahrbar, Schiffbar
{
...
void bewege( )
{
// wie implementieren?
}
}
Wie Klassen zählen auch Interfaces zu den Referenzdatentypen.
Comparable x; // Referenz x, die nach null zeigt
public static void sort(Object[ ] feld, Comparator r);
Jede Klasse, die ein Interface implementiert, ist zuweisungskompatibel zum Interface.
public class A implements Comparable
{
...
}
...
Comparable x;
...
x = new A( );
Nutzung von Interfaces: Im Wesentlichen gibt es drei Möglichkeiten.
- durch eigenes Implementieren
- durch Ableiten von einer Klasse, die das Interface bereits implementiert hat
- durch sogenanntes
composition
public interface S
{
int machWas(String info);
void machNochWas( );
}
1.
public class A implements S
{
public int machWas(String info)
{
// überschreiben !
}
public void machNochWas( )
{
// überschreiben !
}
}
2.
public class B extends A
{
... // Interface S ist automatisch implementiert
...
}
3.
public class C implements S
{
private A dummy = new A( );
...
public int machWas(String info)
{
return dummy.machWas(info);
}
void machNochWas( )
{
dummy.machNochWas( );
}
}
Es gibt auch Interfaces,
die leer sind und weder Methoden noch Konstanten enthalten,
sogenannte "Marker-Interfaces"
(Bsp.: Cloneable
).
Sie dienen dazu, einen Hinweis zu geben, ob eine Eigenschaft in einer Klasse vorhanden ist oder nicht.
public class A implements Cloneable
{
public Object clone( )
{
// sinnvoll überschrieben
}
}
public class B
{
public Object clone( )
{
// CloneNotSupportedException !
}
}