Wirtschaftsinformatik (Bachelor-Studiengang): Programmierung (2. Semester)

Sie sind hier: StartseiteWirtschaftsinformatikProgrammierung Java (Kurs 2): Abstrakte Klassen

HB / CM, Kurs vom 01.10.2002 - 31.03.2003

Programmierung Java (Kurs 2): Abstrakte Klassen: Abstrakte Klassen (Warum abstrakt, d.h. keine Instanzen?, Wie werden abstrakte Klassen deklariert?), Interface (Schnittstellen) (Andererseits stellt sich die Frage, wonach sort A[ ] sortieren soll?, Wie macht man eine beliebige Klasse A "sortierbar"?, Warum nicht - statt eines neuen Konzeptes "Interfaces" - einfach von einer abstrakten Klasse erben?, Anwendung von Interfaces).

  1. Abstrakte Klassen
  2. Interface (Schnittstellen)

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?

  1. sprich aus der Klasse Tier entfernen
  2. sprich in Tier belassen, aber keine Anweisung schreiben

Beide Lösungen sind schlecht.

ad1: Folgendes Konstrukt ist dann nicht mehr möglich:

Quelltext überspringen

...
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):

  1. Mindestens eine der eigenen Methoden ist abstract.
  2. C hat von einer Klasse A eine abstrakte Methode geerbt, aber nicht überschrieben.
  3. 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(...);

Zum Menü Wirtschaftsinformatik | Zum Seitenanfang

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.

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:

A wird somit sortierbar dadurch, dass

  1. das Interface Comparable übernommen wird
  2. 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:

Innerhalb Interfaces können vier Dinge deklariert werden:

  1. konstante Attribute
  2. abstrakte Methoden
  3. (geschachtelte) Klassen
  4. (geschachtelte) Interfaces

Quelltext überspringen

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.

Die Methoden eines Interfaces sind immer public abstract.

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.

Quelltext überspringen

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.

Quelltext überspringen

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.

  1. durch eigenes Implementieren
  2. durch Ableiten von einer Klasse, die das Interface bereits implementiert hat
  3. durch sogenanntes composition

Quelltext überspringen

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.

Quelltext überspringen

public class A implements Cloneable
{
public Object clone( )
{
// sinnvoll überschrieben
}
}
public class B
{
public Object clone( )
{
// CloneNotSupportedException !
}
}