Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort
1 Java ist auch eine Sprache
2 Sprachbeschreibung
3 Klassen und Objekte
4 Der Umgang mit Zeichenketten
5 Mathematisches
6 Eigene Klassen schreiben
7 Angewandte Objektorientierung
8 Exceptions
9 Die Funktionsbibliothek
10 Threads und nebenläufige Programmierung
11 Raum und Zeit
12 Datenstrukturen und Algorithmen
13 Dateien und Datenströme
14 Die eXtensible Markup Language (XML)
15 Grafische Oberflächen mit Swing
16 Grafikprogrammierung
17 Netzwerkprogrammierung
18 Verteilte Programmierung mit RMI und Web-Services
19 JavaServer Pages und Servlets
20 Applets
21 Midlets und die Java ME
22 Datenbankmanagement mit JDBC
23 Reflection und Annotationen
24 Logging und Monitoring
25 Sicherheitskonzepte
26 Java Native Interface (JNI)
27 Dienstprogramme für die Java-Umgebung
A Die Begleit-DVD
Stichwort

Download:
- ZIP, ca. 12,5 MB
Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom
Programmieren mit der Java Standard Edition Version 6
Buch: Java ist auch eine Insel

Java ist auch eine Insel
7., aktualisierte Auflage
geb., mit DVD (November 2007)
1.492 S., 49,90 Euro
Galileo Computing
ISBN 978-3-8362-1146-8
Pfeil 6 Eigene Klassen schreiben
Pfeil 6.1 Eigene Klassen mit Eigenschaften deklarieren
Pfeil 6.1.1 Attribute deklarieren
Pfeil 6.1.2 Methoden deklarieren
Pfeil 6.1.3 Die this-Referenz
Pfeil 6.2 Privatsphäre und Sichtbarkeit
Pfeil 6.2.1 Für die Öffentlichkeit: public
Pfeil 6.2.2 Paketsichtbar
Pfeil 6.2.3 Kein Public Viewing – Passwörter sind privat
Pfeil 6.2.4 Wieso nicht freie Methoden und Variablen für alle?
Pfeil 6.2.5 Privat ist nicht ganz privat: Es kommt darauf an, wer’s sieht
Pfeil 6.2.6 Zugriffsmethoden für Attribute deklarieren
Pfeil 6.2.7 Setter und Getter nach der JavaBeans-Spezifikation
Pfeil 6.3 Statische Methoden und statische Attribute
Pfeil 6.3.1 Warum statische Eigenschaften sinnvoll sind
Pfeil 6.3.2 Statische Eigenschaften mit static
Pfeil 6.3.3 Statische Eigenschaften über Referenzen nutzen?
Pfeil 6.3.4 Warum die Groß- und Kleinschreibung wichtig ist
Pfeil 6.3.5 Statische Eigenschaften und Objekteigenschaften
Pfeil 6.3.6 Statische Variablen zum Datenaustausch
Pfeil 6.3.7 Statische Blöcke als Klasseninitialisierer
Pfeil 6.4 Konstanten und Aufzählungen
Pfeil 6.4.1 Konstanten über öffentliche statische finale Variablen
Pfeil 6.4.2 Eincompilierte Belegungen der Klassenvariablen
Pfeil 6.4.3 Typ(un)sicherere Aufzählungen
Pfeil 6.4.4 Aufzählungen mit enum
Pfeil 6.5 Objekte anlegen und zerstören
Pfeil 6.5.1 Konstruktoren schreiben
Pfeil 6.5.2 Der Standard-Konstruktor
Pfeil 6.5.3 Parametrisierte und überladene Konstruktoren
Pfeil 6.5.4 Konstruktor nimmt ein Objekt vom eigenen Typ an (Copy-Konstruktor)
Pfeil 6.5.5 Einen anderen Konstruktor der gleichen Klasse aufrufen
Pfeil 6.5.6 Initialisierung der Objekt- und Klassenvariablen
Pfeil 6.5.7 Finale Werte im Konstruktor und in statischen Blöcken setzen
Pfeil 6.5.8 Exemplarinitialisierer (Instanzinitialisierer)
Pfeil 6.5.9 Ihr fehlt uns nicht – der Garbage-Collector
Pfeil 6.5.10 Implizit erzeugte String-Objekte
Pfeil 6.5.11 Private Konstruktoren, Utility-Klassen, Singleton, Fabriken
Pfeil 6.6 Assoziationen zwischen Objekten
Pfeil 6.6.1 Unidirektionale 1:1-Beziehung
Pfeil 6.6.2 Bidirektionale 1:1-Beziehungen
Pfeil 6.6.3 Unidirektionale 1:n-Beziehung
Pfeil 6.7 Vererbung
Pfeil 6.7.1 Vererbung in Java
Pfeil 6.7.2 Spielobjekte modelliert
Pfeil 6.7.3 Einfach- und Mehrfachvererbung
Pfeil 6.7.4 Sichtbarkeit protected
Pfeil 6.7.5 Konstruktoren in der Vererbung und super
Pfeil 6.7.6 Automatische und explizite Typanpassung
Pfeil 6.7.7 Das Substitutionsprinzip
Pfeil 6.7.8 Typen mit dem binären Operator instanceof testen
Pfeil 6.7.9 Methoden überschreiben
Pfeil 6.7.10 Mit super an die Eltern
Pfeil 6.7.11 Kovariante Rückgabetypen
Pfeil 6.7.12 Array-Typen und Kovarianz
Pfeil 6.7.13 Zusammenfassung zur Sichtbarkeit
Pfeil 6.8 Dynamisches Binden
Pfeil 6.8.1 Unpolymorph bei privaten, statischen und finalen Methoden
Pfeil 6.8.2 Polymorphie bei Konstruktoraufrufen
Pfeil 6.8.3 Finale Klassen
Pfeil 6.8.4 Nicht überschreibbare (finale) Methoden
Pfeil 6.9 Abstrakte Klassen und abstrakte Methoden
Pfeil 6.9.1 Abstrakte Klassen
Pfeil 6.9.2 Abstrakte Methoden
Pfeil 6.10 Schnittstellen
Pfeil 6.10.1 Deklarieren von Schnittstellen
Pfeil 6.10.2 Implementieren von Schnittstellen
Pfeil 6.10.3 Markierungsschnittstellen
Pfeil 6.10.4 Ein Polymorphie-Beispiel mit Schnittstellen
Pfeil 6.10.5 Die Mehrfachvererbung bei Schnittstellen
Pfeil 6.10.6 Keine Kollisionsgefahr bei Mehrfachvererbung
Pfeil 6.10.7 Erweitern von Interfaces – Subinterfaces
Pfeil 6.10.8 Vererbte Konstanten bei Schnittstellen
Pfeil 6.10.9 Schnittstellenmethoden, die nicht implementiert werden müssen
Pfeil 6.10.10 Abstrakte Klassen und Schnittstellen im Vergleich
Pfeil 6.11 Geschachtelte (innere) Klassen, Schnittstellen, Aufzählungen
Pfeil 6.11.1 Statische innere Klassen und Schnittstellen
Pfeil 6.11.2 Mitglieds- oder Elementklassen
Pfeil 6.11.3 Lokale Klassen
Pfeil 6.11.4 Anonyme innere Klassen
Pfeil 6.11.5 this und Vererbung
Pfeil 6.12 Generische Datentypen
Pfeil 6.12.1 Einfache Klassenschablonen
Pfeil 6.12.2 Einfache Methodenschablonen
Pfeil 6.12.3 Umsetzen der Generics, Typlöschung und Raw-Types
Pfeil 6.12.4 Einschränken der Typen
Pfeil 6.12.5 Generics und Vererbung, Invarianz
Pfeil 6.12.6 Wildcards
Pfeil 6.13 Die Spezial-Oberklasse Enum
Pfeil 6.13.1 Methoden auf Enum-Objekten
Pfeil 6.13.2 enum mit eigenen Konstruktoren und Methoden
Pfeil 6.14 Dokumentationskommentare mit JavaDoc
Pfeil 6.14.1 Einen Dokumentationskommentar setzen
Pfeil 6.14.2 Mit javadoc eine Dokumentation erstellen
Pfeil 6.14.3 HTML-Tags in Dokumentationskommentaren
Pfeil 6.14.4 Generierte Dateien
Pfeil 6.14.5 Dokumentationskommentare im Überblick
Pfeil 6.14.6 JavaDoc und Doclets
Pfeil 6.14.7 Veraltete (deprecated) Klassen, Konstruktoren und Methoden


Galileo Computing - Zum Seitenanfang

6.10 Schnittstellen Zur nächsten ÜberschriftZur vorigen Überschrift

Da Java nur Einfachvererbung kennt, ist es schwierig, Klassen mehrere Typen zu geben. Das kann immer nur in einer Reihe geschehen, also etwa GameObject erbt von Object, Building erbt von GameObject, Castle erbt von Building usw. Es wird schwierig, an einer Stelle zu sagen, dass ein Building ein GameObject ist, aber zum Beispiel noch zusätzlich einen Typ Preis haben soll, was nur nicht gleich alle Spielobjekte haben sollen. Denn soll eine Klasse auf einer Ebene von mehreren Typen erben, geht das durch die Einfachvererbung nicht. Da es aber möglich sein soll, dass in der objektorientierten Modellierung eine Klasse mehrere Typen in einem Schritt besitzt, gibt es das Konzept der Schnittstelle (engl. interface). Eine Klasse kann dann neben der Oberklasse eine beliebige Anzahl Schnittstellen implementieren und auf diese Weise weitere Typen sammeln.


Galileo Computing - Zum Seitenanfang

6.10.1 Deklarieren von Schnittstellen Zur nächsten ÜberschriftZur vorigen Überschrift

Eine Schnittstelle enthält keine Implementierungen, sondern deklariert nur den Kopf einer Methode – also Modifizierer, den Rückgabetyp und Signatur – ohne Rumpf.

Sollen in einem Spiel gewisse Dinge käuflich sein, haben sie einen Preis. Eine Schnittstelle Buyable soll allen Klassen die Methode price() vorschreiben.

Listing 6.80 com/tutego/insel/game/vk/Buyable.java, Buyable

interface Buyable 
{ 
  double price(); 
}

Die Deklaration einer Schnittstelle erinnert an eine abstrakte Klasse mit abstrakten Methoden, nur steht an Stelle von class das Schlüsselwort interface. Da alle Methoden in Schnittstellen automatisch abstrakt und öffentlich sind, akzeptiert der Compiler das redundante abstract und public, doch die Modifizierer sollten nicht geschrieben werden. Die von den Schnittstellen deklarierten Operationen sind – wie auch bei abstrakten Methoden – mit einem Semikolon abgeschlossen und haben niemals eine Implementierung.

Eine Schnittstelle darf keinen Konstruktor deklarieren. Das ist auch klar, da Exemplare von Schnittstellen nicht erzeugt werden können, sondern nur von den konkreten implementierenden Klassen.


Hinweis Hinweis Der Name einer Schnittstelle endet oft mit auf -ble (Accessible, Adjustable, Runnable). Er beginnt üblicherweise nicht mit einem Präfix wie »I«, obwohl die Eclipse-Entwickler diese Namenskonvention nutzen.


Obwohl in einer Schnittstelle keine Funktionen ausprogrammiert werden und keine Objektvariablen deklariert werden dürfen, sind static final-Variablen (benannte Konstanten) in einer Schnittstelle erlaubt, statische Funktionen jedoch nicht.

Eclipse
Existiert eine Klasse, in der Methoden in einer neuen Schnittstelle deklariert werden sollen, lässt sich RefactorExtract Interface... einsetzen. Es folgt ein Dialog, der uns Methoden auswählen lässt, die später in der neuen Schnittstelle deklariert werden. Eclipse legt die Schnittstelle automatisch an und lässt die Klasse die Schnittstelle implementieren. Dort, wo es möglich ist, erlaubt Eclipse, dass die konkrete Klasse durch die Schnittstelle ersetzt wird.


Galileo Computing - Zum Seitenanfang

6.10.2 Implementieren von Schnittstellen Zur nächsten ÜberschriftZur vorigen Überschrift

Möchte eine Klasse eine Schnittstelle verwenden, so folgt hinter dem Klassennamen das Schlüsselwort implements und dann der Name der Schnittstelle. Die Ausdrucksweise ist dann: »Klassen werden vererbt und Schnittstellen implementiert.«

Für unsere Spielwelt sollen die Klassen Chocolate und Magazine die Schnittstelle Buyable implementieren.

Listing 6.81 com/tutego/insel/game/vk/Chocolate.java, Chocolate

public class Chocolate implements Buyable 
{ 
  @Override public double price() 
  { 
    return 0.69; 
  } 
}

Während Chocolate nur die Schnittstelle Buyable implementiert, soll Magazine zusätzlich ein GameObject sein:

Listing 6.82 com/tutego/insel/game/vk/Magazine.java, Magazine

public class Magazine extends GameObject implements Buyable 
{ 
  double price; 
 
  @Override public double price() 
  { 
    return price; 
  } 
}

Es ist also kein Problem – und bei uns so gewünscht –, wenn eine Klasse eine andere Klasse erweitert und zusätzlich Operationen aus Schnittstellen implementiert.


Hinweis Hinweis Bei der Implementierung einer Schnittstelle müssen die Methoden in den Unterklassen öffentlich implementiert werden, da die Methoden in Schnittstellen immer automatisch public sind. Seit Java 6 ist die Annotation @Override empfohlen; unter Java 5 war sie noch verboten.


Implementiert eine Klasse nicht alle Operationen aus den Schnittstellen, so erbt sie damit abstrakte Funktionen und muss selbst wieder als abstrakt gekennzeichnet werden.

Eclipse
Eclipse zeigt bei der Tastenkombination Keyboard Ctrl + Keyboard T eine Typhierarchie an, also Oberklassen stehen oben und Unterklassen unten. Wird in dieser Ansicht erneut Keyboard Ctrl + Keyboard T gedrückt, wird die Ansicht umgedreht, dann stehen die Obertypen unten, was den Vorteil hat, dass auch die implementierte Schnittstelle unter den Obertypen ist.


Galileo Computing - Zum Seitenanfang

6.10.3 Markierungsschnittstellen Zur nächsten ÜberschriftZur vorigen Überschrift

Auch Schnittstellen ohne Methoden sind möglich. Diese leeren Schnittstellen werden Markierungsschnittstellen (engl. marker interface) genannt. Sie sind nützlich, da mit instanceof leicht überprüft werden kann, ob sie einen gewollten Typ einnehmen.


Beispiel Beispiel Die Sun-Bibliothek bringt einige Markierungsschnittstellen schon mit, etwa: java.util.RandomAccess, java.rmi.Remote oder java.io.Serializable.

Listing 6.83 java/lang/Serializable.java

package java.io; 
interface Serializable 
{ 
}

Implementiert eine Klasse Serializable, so lassen sich die Zustände eines Objekts in einen Datenstrom schreiben. (Mehr dazu in Kapitel 13.)



Galileo Computing - Zum Seitenanfang

6.10.4 Ein Polymorphie-Beispiel mit Schnittstellen Zur nächsten ÜberschriftZur vorigen Überschrift

Obwohl Schnittstellen auf den ersten Blick nichts »bringen« – Programmierer wollen gerne etwas vererbt bekommen, damit sie Implementierungsarbeit sparen können –, sind sie eine enorm wichtige Erfindung, da sich über Schnittstellen ganz unterschiedliche Sichten auf ein Objekt beschreiben lassen. Jede Schnittstelle ermöglicht eine neue Sicht auf das Objekt, eine Art Rolle. Implementiert eine Klasse diverse Schnittstellen, können ihre Exemplare in verschiedenen Rollen auftreten. Hier wird erneut das Substitutionsprinzip wichtig, bei dem ein mächtigeres Objekt verwendet wird, obwohl je nach Kontext nur die Funktion der der Schnittstellen erwartet wird.

Mit Magazine und Chocolate haben wir zwei Klassen, die Buyable implementieren. Damit existieren zwei Klassen, die einen gemeinsamen Typ und beide eine gemeinsame Funktion price() besitzen.

Buyable b1 = new Magazine(); 
Buyable b2 = new Chocolate(); 
System.out.println( b1.price() ); 
System.out.println( b2.price() );

Für Buyable wollen wir eine Funktion schreiben, die den Preis einer Sammlung kaufbarer Objekte berechnet. Sie soll wie folgt aufgerufen werden:

Listing 6.84 com/tutego/insel/game/vk/Playground.java, main()

Magazine madMag = new Magazine(); 
madMag.price = 2.50; 
Buyable schoki = new Chocolate(); 
Magazine maxim = new Magazine(); 
maxim.price = 3.00; 
System.out.printf( "%.2f", PriceUtils.calculateSum( madMag, maxim, schoki ) );  
// 6,19

Damit calculateSum() eine beliebige Anzahl Argumente, aber mindestens eins, annehmen kann, realisieren wir die Funktion mit einem Vararg:

Listing 6.85 com/tutego/insel/game/vk/PriceUtils.java, calculateSum()

static double calculateSum( Buyable price1, Buyable... prices ) 
{ 
  double result = price1.price(); 
 
  for ( Buyable price : prices ) 
    result += price.price(); 
 
  return result; 
}

Die Methode nimmt käufliche Dinge an, wobei es ihr völlig egal ist, um welche es sich dabei handelt. Was zählt, ist die Tatsache, dass die Elemente die Schnittstelle Buyable implementieren.

Die Polymorphie tritt schon in der ersten Anweisung price1.price() auf. Auch später rufen wir auf jedem Objekt, das Buyable implementiert, die Funktion price() auf. Indem wir die unterschiedlichen Werte summieren, bekommen wir den Gesamtpreis der Elemente aus der Parameterliste.


Tipp Tipp Wie schon erwähnt, sollte der Typ einer Variable immer der kleinste nötige sein. Dabei sind Schnittstellen als Variablentypen nicht ausgenommen. Entwickler, die alle ihre Variablen vom Typ einer Schnittstelle deklarieren, wenden das Konzept »Programmieren gegen Schnittstellen« an. Sie binden sich also nicht an eine spezielle Implementierung, sondern an einen Basistyp.


Im Zusammenhang mit Schnittstellen bleibt zusammenfassend zu sagen, dass hier dynamisches Binden pur auftaucht.


Galileo Computing - Zum Seitenanfang

6.10.5 Die Mehrfachvererbung bei Schnittstellen Zur nächsten ÜberschriftZur vorigen Überschrift

Bei Klassen gibt es die Einschränkung, dass nur von einer direkten Oberklasse abgeleitet werden darf – egal, ob sie abstrakt ist oder nicht. Wird hingegen eine Schnittstelle implementiert, dann werden nicht mehr aus verschiedenen Quellen unterschiedliche Implementierungen für dieselbe Methode angeboten, was zu Problemen führen kann. Ohne Schwierigkeiten kann eine Klasse mehrere Schnittstellen implementieren. Dies wird gelegentlich als »Mehrfachvererbung in Java« bezeichnet. Auf diese Weise besitzt die Klasse ganz unterschiedliche Typen, da sie nun instanceof der Oberklasse – beziehungsweise der indirekten Oberklassen – sowie der Schnittstellen ist.

Ein Magazine soll zunächst ein GameObject sein. Dann soll es nicht nur die Schnittstelle Buyable und damit die Methode price()implementieren, sondern sich auch mit anderen Magazinen vergleichen lassen. Dazu gibt es schon eine passende Schnittstelle in der Java-Bibliothek: java.lang.Comparable. Die Schnittstelle Comparable fordert, dass unser Magazin die Methode int compareTo(Magazine) implementiert. Der Rückgabewert der Methode zeigt an, wie das eigene Magazin zum anderen aufgestellt ist. Wir wollen definieren, dass das günstigere Magazin vor einem teureren steht.

Listing 6.86 com/tutego/insel/game/vl/GameObject.java, GameObject

abstract class GameObject implements Serializable 
{ 
  protected String name; 
 
  protected GameObject( String name ) 
  { 
    this.name = name; 
 
  } 
}

GameObject soll die Markierungsschnittstelle Serializable implementieren, sodass dann alle Unterklassen von GameObject ebenfalls vom Typ Serializable sind. Die Markierungsschnittstelle schreibt nichts vor, daher gibt es keine spezielle überschriebene Funktion.

Listing 6.87 com/tutego/insel/game/vl/Buyable.java, Buyable

interface Buyable 
{ 
  double price(); 
}

Listing 6.88 com/tutego/insel/game/vl/Magazine.java, Magazine

public class Magazine extends GameObject implements Buyable, Comparable<Magazine> 
{ 
  private double price; 
 
  public Magazine( String name, double price ) 
  { 
    super( name ); 
    this.price = price; 
  } 
 
  @Override public double price() 
  { 
    return price; 
  } 
 
  @Override public int compareTo( Magazine that ) 
  { 
    return this.price() < that.price() ? –1 : (this.price() > that.price() ? +1 : 0); 
  } 
 
  @Override public String toString() 
  { 
    return name + " " + price; 
  } 
}

Die Implementierung nutzt Generics mit Comparable<Buyable>, was wir genauer erst später lernen, aber an der Stelle schon einmal nutzen wollen. Der Hintergrund ist, dass Comparable dann genau weiß, mit welchem anderen Typ der Vergleich stattfinden soll.

Durch diese »Mehrfachvererbung« bekommt Magazine mehrere Typen, sodass sich je nach Sichtweise schreiben lässt:

Magazine             m1  = new Magazine( "Mad Magazine", 2.50 ); 
GameObject           m2  = new Magazine( "Mad Magazine", 2.50 ); 
Object               m3  = new Magazine( "Mad Magazine", 2.50 ); 
Buyable              m4  = new Magazine( "Mad Magazine", 2.50 ); 
Comparable<Magazine> m5  = new Magazine( "Mad Magazine", 2.50 ); 
Serializable         m6  = new Magazine( "Mad Magazine", 2.50 );

Die Konsequenz davon:

  • Im Fall m1 sind alle Methoden der Schnittstellen verfügbar, also price() und compareTo().
  • Über m2 ist keine Schnittstellenmethode verfügbar, und nur die geschützte Variable name ist vorhanden.
  • Mit m3 sind alle Bezüge zu Spielobjekten verloren.
  • Die Variable m4 ist vom Typ Buyable, sodass es price() gibt, jedoch kein compareTo().
  • Mit m5 gibt es ein compareTo(), aber keinen Preis.
  • Da Magazine die Klasse GameObject erweitert und darüber auch vom Typ Serialize ist, lässt sich keine besondere Funktion aufrufen, denn Serializable ist eine Markierungsschnittstelle ohne Operationen.

Ein kleines Beispiel zeigt abschließend die Anwendung der Funktion compareTo() der Schnittstelle Comparable und price() der Schnittstelle Buyable.

Listing 6.89 com/tutego/insel/game/vl/Playground.java, main()

Magazine spiegel = new Magazine( "Spiegel", 3.50 ); 
Magazine madMag  = new Magazine( "Mad Magazine", 2.50 ); 
Magazine maxim   = new Magazine( "Maxim", 3.00 ); 
Magazine neon    = new Magazine( "Neon", 3.00 ); 
Magazine ct      = new Magazine( "c't", 3.30 );

Da wir einem Magazin so viele Sichten gegeben haben, können wir es natürlich mit unserer früheren Funktion calculateSum() aufrufen, da jedes Magazine ja Buyable ist:

System.out.println( PriceUtils.calculateSum( spiegel, madMag, ct ) ); // 9.3
Und die Magazine können wir vergleichen:
System.out.println( spiegel.compareTo( ct ) );  // 1 
System.out.println( ct.compareTo( spiegel ) );  // –1 
System.out.println( maxim.compareTo( neon ) );  // 0

So wie es der Funktion calculateSum() egal ist, was für Buyable-Objekte konkret übergeben werden, gibt es auch für Comparable einen sehr nützlichen Anwendungsfall: Sortieren. Einem Sortierverfahren ist es egal, was für Objekte genau es sortiert, solange die Objekte sagen, ob sie vor oder hinter einem anderen Objekt liegen.

Magazine[] mags = new Magazine[] { spiegel, madMag, maxim, neon, ct }; 
Arrays.sort( mags ); 
System.out.println( Arrays.toString( mags ) ); // [Mad Magazine 2.5, Maxim 3.0,  
                                               // Neon 3.0, c't 3.3, Spiegel 3.5]

Die Methode Arrays.sort() erwartet ein Feld, dessen Elemente Comparable sind. Der Sortieralgorithmus macht Vergleiche über compareTo(), muss aber sonst über die Objekte nichts wissen. Unsere Magazine mit den unterschiedlichen Typen können also sehr flexibel in unterschiedlichen Kontexten eingesetzt werden. Es muss somit für das Sortieren keine Spezialsortierfunktion geschrieben werden, die nur Magazine sortieren kann, oder eine Funktion zur Berechnung einer Summe, die nur auf Magazinen arbeitet. Wir modellieren die unterschiedlichen Anwendungsszenarien mit jeweils unterschiedlichen Schnittstellen, die Unterschiedliches von dem Objekt erwarten.


Galileo Computing - Zum Seitenanfang

6.10.6 Keine Kollisionsgefahr bei Mehrfachvererbung Zur nächsten ÜberschriftZur vorigen Überschrift

Das Dilemma bei der Mehrfachvererbung von Klassen wäre, dass zwei Oberklassen die gleiche Funktion mit zwei unterschiedlichen Implementierungen vererben könnten. Die Unterklasse wüsste dann nicht, welche Logik sie erbt. Bei den Schnittstellen gibt es das Problem nicht, denn auch wenn zwei implementierende Schnittstellen die gleiche Funktion vorschreiben würden, gäbe es keine zwei verschiedenen Implementierungen von Anwendungslogik. Die implementierende Klasse bekommt sozusagen zweimal die Aufforderung, die Operation zu implementieren. So wie bei folgendem Beispiel: Ein Politiker muss verschiedene Dinge vereinen; er muss sympathisch, aber auch durchsetzungsfähig handeln können.

Listing 6.90 Politician.java

interface Likeable 
{ 
  void act(); 
} 
 
interface Assertive 
{ 
  void act(); 
} 
 
public class Politician implements Likeable, Assertive 
{ 
  public void act() 
  { 
    // Implementation 
  } 
}

Zwei Schnittstellen schreiben die gleiche Operation vor. Eine Klasse implementiert diese beiden Schnittstellen und muss beiden Vorgaben gerecht werden.


Galileo Computing - Zum Seitenanfang

6.10.7 Erweitern von Interfaces – Subinterfaces Zur nächsten ÜberschriftZur vorigen Überschrift

Ein Subinterface ist die Erweiterung eines anderen Interfaces. Diese Erweiterung erfolgt – wie bei der Vererbung – durch das Schlüsselwort extends.

interface Disgusting 
{ 
  double disgustingValue(); 
}
interface Stinky extends Disgusting 
{ 
  double olf(); 
}

Die Schnittstelle modelliert Stinkiges, was besonders abstoßend ist. Zusätzlich soll die Stinkquelle die Stärke der Stinkigkeit in der Einheit Olf angeben. Eine Klasse, die nun Stinky implementiert, muss die Methoden aus beiden Schnittstellen implementieren, demnach die Methode disgustingValue() aus Disgusting sowie die Operation olf(), die in Stinky selbst angegeben wurde. Ohne die Implementierung beider Methoden wird eine implementierende Klasse abstrakt sein müssen.


Galileo Computing - Zum Seitenanfang

6.10.8 Vererbte Konstanten bei Schnittstellen Zur nächsten ÜberschriftZur vorigen Überschrift

Schnittstellen können Variablen besitzen, die jedoch, wie wir gesehen haben, immer automatisch statisch und final, also Konstanten sind. Diese Konstanten können einer anderen Schnittstelle vererbt werden. Dabei gibt es einige kleine Einschränkungen.

Wir wollen an einem Beispiel sehen, wie sich die Vererbung auswirkt, wenn gleiche Bezeichner in den Unterschnittstellen erneut verwendet werden.

Listing 6.91 Colors.java

interface BaseColors 
{ 
  int RED     = 1; 
  int GREEN   = 2; 
  int BLUE    = 3; 
} 
 
interface CarColors extends BaseColors 
{ 
  int BLACK   = 10; 
  int PURPLE  = 11; 
} 
 
interface CoveringColors extends BaseColors 
{ 
  int PURPLE  = 11; 
  int BLACK   = 20; 
  int WHITE   = 21; 
} 
 
interface AllColors extends CarColors, CoveringColors 
{ 
  int WHITE  = 30; 
} 
 
 
public class Colors 
{ 
  @SuppressWarnings("all") 
  public static void main( String[] args ) 
  { 
    System.out.println( CarColors.RED );        // 1 
    System.out.println( AllColors.RED );        // 1 
    System.out.println( CarColors.BLACK );      // 10 
    System.out.println( CoveringColors.BLACK ); // 20 
 
//    System.out.println( AllColors.BLACK );  // The field AllColors.BLACK  
                                              // is ambiguous 
//    System.out.println( AllColors.PURPLE ); // The field AllColors.PURPLE  
                                              // is ambiguous 
  } 
}

Die erste wichtige Tatsache ist, dass Schnittstellen ohne Fehler übersetzt werden können. Doch das Programm zeigt weitere Eigenschaften:

1. Schnittstellen vererben ihre Eigenschaften an die Unterschnittstellen. CarColors erbt die Farbe rot aus BaseColors.
2. Erbt eine Schnittstelle von mehreren Oberklassen, die jeweils ein bestimmtes Attribut von einer gemeinsamen Oberklasse beziehen, so ist dies kein Fehler. So erbt etwa AllColors von CarColors und CoveringColors die Farbe Rot.
3. Konstanten dürfen überschrieben werden. CoveringColors überschreibt die Farbe BLACK aus CarColors mit dem Wert 20. Auch PURPLE wird überschrieben, obwohl die Konstante mit dem gleichen Wert belegt ist. Wird jetzt der Wert CoveringColors.BLACK verlangt, liefert die Umgebung den Wert 20.
4. Unterschnittstellen können aus zwei Oberschnittstellen die Attribute gleichen Namens übernehmen, auch wenn sie einen unterschiedlichen Wert haben. Das zeigt sich an den beiden Beispielen AllColors.BLACK und AllColors.PURPLE. Bei der Benutzung muss ein qualifizierter Name verwendet werden, der deutlich macht, welches Attribut gemeint ist, also zum Beispiel CarColors.BLACK, denn die Farbe ist in den Oberschnittstellen CarColors und CoveringColors unterschiedlich initialisiert. Ähnliches gilt für die Farbe PURPLE. Obwohl PURPLE in beiden Fällen den Wert 11 trägt, ist das nicht erlaubt. Das ist ein guter Schutz gegen Fehler, denn wenn der Compiler dies durchlassen würde, könnte sich im Nachhinein die Belegung von PURPLE in CarColors oder CoveringColors ohne Neuübersetzung aller Klassen ändern und zu Schwierigkeiten führen. Diesen Fehler – die Oberschnittstellen haben für eine Konstante unterschiedliche Werte – müsste die Laufzeitumgebung erkennen. Zudem kann und sollte der Compiler für alle Konstanten die Werte direkt einsetzen.

Galileo Computing - Zum Seitenanfang

6.10.9 Schnittstellenmethoden, die nicht implementiert werden müssen Zur nächsten ÜberschriftZur vorigen Überschrift

Bis auf eine Ausnahme muss eine Klasse, zu der Exemplare erzeugt werden sollen, alle Methoden der Schnittstellen implementieren. Eine Ausnahme ergibt sich wieder aus der Tatsache, dass jede Schnittstelle die Methoden von Object annimmt. Sehen wir uns den Programmcode der Schnittstelle Comparator an, die im Paket java.util deklariert ist:

package java.util; 
 
public interface Comparator<T> 
{ 
  int compare( T o1, T o2 ); 
  boolean equals( Object obj ); 
}

Wir entdecken, dass dort die equals()-Methode vorgeschrieben wird. Der erste Gedanke ist, nun eine Klasse zu schreiben, die compare() und equals() implementieren muss. Dies ist hier allerdings nicht nötig, da equals()schon eine Methode ist, die jedes Objekt besitzt. Daraus ergibt sich, dass nicht alle Methoden ausprogrammiert werden müssen. (Eventuell überschreiben wir equals(), wenn uns die Semantik von equals() in Object nicht gefällt.) Außerdem lässt sich eine Schnittstelle angeben, die die Methoden von Object auflistet. Auch dann müsste keine Methode implementiert werden. Bleibt die Frage, warum denn Comparator eine equals()-Methode vorschreibt, wenn diese doch nicht implementiert zu werden braucht. Um uns zu verwirren? Nein. Der Sinn besteht einfach darin, die Funktionsweise in der Dokumentation genau anzugeben. Eine Java-Dokumentation kann nur generiert werden, wenn auch eine Funktion im Quellcode vorhanden ist. Die Entwickler wollten bei equals() in der Schnittstelle Comparator noch einmal bewusst auf die Funktion hinweisen, dass equals() zwei Comparator-Objekte daraufhin vergleicht, ob beide die gleiche Sortierfolge verwenden, und nicht (wie wir annehmen könnten) zwei Objekte auf Gleichheit testet.


Galileo Computing - Zum Seitenanfang

6.10.10 Abstrakte Klassen und Schnittstellen im Vergleich topZur vorigen Überschrift

Eine abstrakte Klasse und eine Schnittstelle sind sich sehr ähnlich: Beide schreiben den Unterklassen beziehungsweise den implementierten Klassen Operationen vor, die sie implementieren müssen. Ein wichtiger Unterschied ist jedoch der, dass beliebig viele Schnittstellen implementiert werden können, doch nur eine Klasse – sei sie abstrakt oder nicht – erweitert werden kann. Des Weiteren bieten sich abstrakte Klassen meist im Refactoring oder in der Design-Phase an, wenn Gemeinsamkeiten in einer Oberklasse ausgelagert werden sollen. Abstrakte Klassen können zusätzlichen Programmcode enthalten, was Schnittstellen nicht können. Auch nachträgliche Änderungen an Schnittstellen sind nicht einfach: einer abstrakten Klasse kann eine konkrete Methode mitgegeben werden, was zu keiner Quellcodeanpassung für Unterklassen führt.

Ein Beispiel: Ist eine Schnittstelle oder eine abstrakte Klasse besser, um folgende Operation zu deklarieren?

abstract class Timer                          interface Timer 
{                                             { 
  abstract long getTimeInMillis();              long getTimeInMillis(); 
}                                             }

Eine abstrakte Klasse hätte den Vorteil, dass später einfacher eine Methode wie getTimeInSeconds() eingeführt werden kann, die konkret sein darf. Würde diese angenehme Hilfsoperation in einer Schnittstelle vorgeschrieben, so müssten alle Unterklassen diese Implementierung immer neu einführen, wobei sie doch schon in der abstrakten Oberklasse einfach programmiert werden könnte:

abstract class Timer 
{ 
  abstract long getTimeInMillis(); 
 
  long getTimeInSeconds() 
  { 
    return getTimeInMillis() * 1000; 
  } 
}


Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.






<< zurück



Copyright © Galileo Press 2008
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de