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 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Der Umgang mit Zeichenketten
5 Eigene Klassen schreiben
6 Exceptions
7 Äußere.innere Klassen
8 Besondere Klassen der Java SE
9 Generics<T>
10 Architektur, Design und angewandte Objektorientierung
11 Die Klassenbibliothek
12 Einführung in die nebenläufige Programmierung
13 Einführung in Datenstrukturen und Algorithmen
14 Einführung in grafische Oberflächen
15 Einführung in Dateien und Datenströme
16 Einführung in die <XML>-Verarbeitung mit Java
17 Einführung ins Datenbankmanagement mit JDBC
18 Bits und Bytes und Mathematisches
19 Die Werkzeuge des JDK
A Die Klassenbibliothek
Stichwort

Download:
- openbook, ca. 24,5 MB
- Aufgaben, ca. 1,1 MB
- Programme, ca. 12,8 MB
Buch bestellen
Ihre Meinung?

Spacer
Java ist auch eine Insel von Christian Ullenboom
Das umfassende Handbuch
Buch: Java ist auch eine Insel

Java ist auch eine Insel
Galileo Computing
1308 S., 10., aktualisierte Auflage, geb., mit DVD
ca. 49,90 Euro, ISBN 978-3-8362-1802-3
Pfeil5 Eigene Klassen schreiben
Pfeil5.1 Eigene Klassen mit Eigenschaften deklarieren
Pfeil5.1.1 Attribute deklarieren
Pfeil5.1.2 Methoden deklarieren
Pfeil5.1.3 Die this-Referenz
Pfeil5.2 Privatsphäre und Sichtbarkeit
Pfeil5.2.1 Für die Öffentlichkeit: public
Pfeil5.2.2 Kein Public Viewing – Passwörter sind privat
Pfeil5.2.3 Wieso nicht freie Methoden und Variablen für alle?
Pfeil5.2.4 Privat ist nicht ganz privat: Es kommt darauf an, wer’s sieht *
Pfeil5.2.5 Zugriffsmethoden für Attribute deklarieren
Pfeil5.2.6 Setter und Getter nach der JavaBeans-Spezifikation
Pfeil5.2.7 Paketsichtbar
Pfeil5.2.8 Zusammenfassung zur Sichtbarkeit
Pfeil5.3 Statische Methoden und statische Attribute
Pfeil5.3.1 Warum statische Eigenschaften sinnvoll sind
Pfeil5.3.2 Statische Eigenschaften mit static
Pfeil5.3.3 Statische Eigenschaften über Referenzen nutzen? *
Pfeil5.3.4 Warum die Groß- und Kleinschreibung wichtig ist *
Pfeil5.3.5 Statische Variablen zum Datenaustausch *
Pfeil5.3.6 Statische Eigenschaften und Objekteigenschaften *
Pfeil5.4 Konstanten und Aufzählungen
Pfeil5.4.1 Konstanten über öffentliche statische finale Variablen
Pfeil5.4.2 Typ(un)sichere Aufzählungen *
Pfeil5.4.3 Aufzählungen mit enum
Pfeil5.5 Objekte anlegen und zerstören
Pfeil5.5.1 Konstruktoren schreiben
Pfeil5.5.2 Der vorgegebene Konstruktor (default constructor)
Pfeil5.5.3 Parametrisierte und überladene Konstruktoren
Pfeil5.5.4 Copy-Konstruktor
Pfeil5.5.5 Einen anderen Konstruktor der gleichen Klasse mit this() aufrufen
Pfeil5.5.6 Ihr fehlt uns nicht – der Garbage-Collector
Pfeil5.5.7 Private Konstruktoren, Utility-Klassen, Singleton, Fabriken
Pfeil5.6 Klassen- und Objektinitialisierung *
Pfeil5.6.1 Initialisierung von Objektvariablen
Pfeil5.6.2 Statische Blöcke als Klasseninitialisierer
Pfeil5.6.3 Initialisierung von Klassenvariablen
Pfeil5.6.4 Eincompilierte Belegungen der Klassenvariablen
Pfeil5.6.5 Exemplarinitialisierer (Instanzinitialisierer)
Pfeil5.6.6 Finale Werte im Konstruktor und in statischen Blöcken setzen
Pfeil5.7 Assoziationen zwischen Objekten
Pfeil5.7.1 Unidirektionale 1:1-Beziehung
Pfeil5.7.2 Bidirektionale 1:1-Beziehungen
Pfeil5.7.3 Unidirektionale 1:n-Beziehung
Pfeil5.8 Vererbung
Pfeil5.8.1 Vererbung in Java
Pfeil5.8.2 Spielobjekte modellieren
Pfeil5.8.3 Die implizite Basisklasse java.lang.Object
Pfeil5.8.4 Einfach- und Mehrfachvererbung *
Pfeil5.8.5 Die Sichtbarkeit protected
Pfeil5.8.6 Konstruktoren in der Vererbung und super()
Pfeil5.9 Typen in Hierarchien
Pfeil5.9.1 Automatische und explizite Typanpassung
Pfeil5.9.2 Das Substitutionsprinzip
Pfeil5.9.3 Typen mit dem instanceof-Operator testen
Pfeil5.10 Methoden überschreiben
Pfeil5.10.1 Methoden in Unterklassen mit neuem Verhalten ausstatten
Pfeil5.10.2 Mit super an die Eltern
Pfeil5.10.3 Finale Klassen und finale Methoden
Pfeil5.10.4 Kovariante Rückgabetypen
Pfeil5.10.5 Array-Typen und Kovarianz *
Pfeil5.11 Drum prüfe, wer sich ewig dynamisch bindet
Pfeil5.11.1 Gebunden an toString()
Pfeil5.11.2 Implementierung von System.out.println(Object)
Pfeil5.11.3 Nicht dynamisch gebunden bei privaten, statischen und finalen Methoden
Pfeil5.11.4 Dynamisch gebunden auch bei Konstruktoraufrufen *
Pfeil5.11.5 Eine letzte Spielerei mit Javas dynamischer Bindung und überschatteten Attributen *
Pfeil5.12 Abstrakte Klassen und abstrakte Methoden
Pfeil5.12.1 Abstrakte Klassen
Pfeil5.12.2 Abstrakte Methoden
Pfeil5.13 Schnittstellen
Pfeil5.13.1 Schnittstellen deklarieren
Pfeil5.13.2 Implementieren von Schnittstellen
Pfeil5.13.3 Markierungsschnittstellen *
Pfeil5.13.4 Ein Polymorphie-Beispiel mit Schnittstellen
Pfeil5.13.5 Die Mehrfachvererbung bei Schnittstellen *
Pfeil5.13.6 Keine Kollisionsgefahr bei Mehrfachvererbung *
Pfeil5.13.7 Erweitern von Interfaces – Subinterfaces
Pfeil5.13.8 Konstantendeklarationen bei Schnittstellen
Pfeil5.13.9 Initialisierung von Schnittstellenkonstanten *
Pfeil5.13.10 Abstrakte Klassen und Schnittstellen im Vergleich
Pfeil5.14 Zum Weiterlesen

Galileo Computing - Zum Seitenanfang

5.9 Typen in HierarchienZur nächsten Überschrift

Die Vererbung bringt einiges Neues in Bezug auf Kompatibilität von Typen mit. Dieser Abschnitt beschäftigt sich mit den Fragen, welche Typen kompatibel sind und wie sich ein Typ zur Laufzeit testen lässt.


Galileo Computing - Zum Seitenanfang

5.9.1 Automatische und explizite TypanpassungZur nächsten ÜberschriftZur vorigen Überschrift

Die Klassen Room und Player haben wir als Unterklassen von GameObject modelliert. Die eigene Oberklasse GameObject erweitert selbst keine explizite Oberklasse, sodass implizit java.lang.Object die Oberklasse ist. In GameObject gibt es das Attribut name, das Player und Room erben, und der Raum hat zusätzlich size für die Raumgröße.

Ist-eine-Art-von-Beziehung und die automatische Typanpassung

Mit der Ist-eine-Art-von-Beziehung ist eine interessante Eigenschaft verbunden, die wir bemerken, wenn wir die Zusammenhänge zwischen den Typen beachten:

  • Ein Raum ist ein Spielobjekt.
  • Ein Spieler ist ein Spielobjekt.
  • Ein Spielobjekt ist ein java.lang.Object.
  • Ein Spieler ist ein java.lang.Object.
  • Ein Raum ist ein java.lang.Object.
  • Ein Raum ist ein Raum.
  • Ein Spieler ist ein Spieler.

Kodieren wir das in Java:

Listing 5.65: com/tutego/insel/game/vd/TypeSuptype.java, main()

Player     playerIsPlayer     = new Player();
GameObject
gameObjectIsPlayer = new Player();
Object
objectIsPlayer = new Player();
Room
roomIsRoom = new Room();
GameObject
gameObjectIsRoom = new Room();
Object
objectIsRoom = new Room();

Es gilt also, dass immer dann, wenn ein Typ gefordert ist, auch ein Untertyp erlaubt ist. Der Compiler führt eine implizite Typanpassung durch. Wir werden uns dieses sogenannte liskovsche Substitutionsprinzip im folgenden Abschnitt anschauen.

Was wissen Compiler und Laufzeitumgebung über unser Programm?

Wichtig ist, zu beobachten, dass Compiler und Laufzeitumgebung unterschiedliche Dinge wissen. Durch den new-Operator gibt es zur Laufzeit nur zwei Arten von Objekten: Player und Room. Auch dann, wenn es

GameObject gameObjectIsRoom = new Room();

heißt, referenziert gameObjectIsRoom zur Laufzeit ein Room-Objekt. Der Compiler aber »vergisst« dies und glaubt, gameObjectIsRoom wäre nur ein einfaches GameObject. In der Klasse GameObject ist jedoch nur name deklariert, aber kein Attribut size, obwohl das tatsächliche Room-Objekt natürlich eine size kennt. Auf size können wir aber erst einmal nicht zugreifen:

println( gameObjectIsRoom.name );
println( gameObjectIsRoom.size ); // Fehler gameObjectIsRoom.size cannot
// be resolved or is not a field

Schreiben wir noch einschränkender

Object objectIsRoom = new Room();
println( objectIsRoom.name ); // Fehler objectIsRoom.name cannot be
// resolved or is not a field
println( objectIsRoom.size ); // Fehler objectIsRoom.size cannot be
// resolved or is not a field

so steht hinter der Referenzvariablen objectIsRoom ein vollständiges Room-Objekt, aber weder size noch name sind nutzbar; es bleiben nur die Fähigkeiten aus java.lang.Object.

Begrifflichkeit

Um den Compiler-Typ vom JVM-Typ zu unterscheiden, nutzen einige Buchautoren
die Begriffe Referenztyp und Objekttyp. Im Fall von GameObject p = new Player(); ist GameObject der Referenztyp und Player der Objekttyp. Der Compiler sieht nur den Referenztyp, aber nicht den Objekttyp.

Explizite Typanpassung

Diese Typeinschränkung gilt auch an anderer Stelle. Ist eine Variable vom Typ Room deklariert, können wir die Variable nicht mit einem »kleineren« Typ initialisieren:

GameObject go         = new Room();    // Raum zur Laufzeit
Room cubbyhole = go; // Fehler Type mismatch: cannot convert from
// GameObject to Room

Auch wenn zur Laufzeit go ein Room referenziert, können wir cubbyhole nicht damit initialisieren. Der Compiler kennt go nur unter dem »kleineren« Typ GameObject, und das reicht nicht zur Initialisierung des »größeren« Typs Room.

Es ist aber möglich, das Objekt hinter go durch eine explizite Typumwandlung für den Compiler wieder zu einem vollwertigen Room mit Größe zu machen:

Room       cubbyhole = (Room) go;
System.out.println( cubbyhole.size ); // Room hat das Attribut size

Unmögliche Anpassung und ClassCastException

Dies funktioniert aber lediglich dann, wenn go auch wirklich einen Raum referenziert. Dem Compiler ist das in dem Moment relativ egal, sodass auch Folgendes ohne Fehler compiliert wird:

Listing 5.66: com/tutego/insel/game/vd/ClassCastExceptionDemo.java, main()

GameObject go        = new Player();
Room
cubbyhole = (Room) go; // Fehler ClassCastException
System.out.println( cubbyhole.size );

Zur Laufzeit kommt es bei diesem Kuckucksobjekt zu einer ClassCastException:

Exception in thread "main" java.lang.ClassCastException: com.tutego.insel.game.vd.
Player cannot be cast to com.tutego.insel.game.vd.Room
at com.tutego.insel.game.vd.ClassCastExceptionDemo.main(ClassCastExceptionDemo.java:8)

Galileo Computing - Zum Seitenanfang

5.9.2 Das SubstitutionsprinzipZur nächsten ÜberschriftZur vorigen Überschrift

Stellen wir uns vor, Bekannte kommen ausgehungert von einer Wandertour zurück und fragen: »Haste was zu essen?« Die Frage zielt wohl darauf ab, dass es bei Hunger ziemlich egal ist, was wir anbieten, wichtig ist nur etwas Essbares. Daher können wir Eis, aber auch Frittierfett und gegrillte Heuschrecken anbieten.

Diese Ausgangslage führt uns zu einem wichtigen Konzept in der Objektorientierung: »Wer wenig will, kann viel bekommen.« Genauer gesagt: Wenn Unterklassen wie Player oder Room die Oberklasse GameObject erweitern, können wir überall, wo GameObject gefordert wird, auch einen Player oder Room übergeben, da beide ja vom Typ GameObject sind und wir mit der Unterklasse nur spezieller werden. Auch können wir weitere Unterklassen von Player und Room übergeben, da auch die Unterklasse weiterhin zusätzlich das »Gen« GameObject in sich trägt. Alle diese Dinge wären vom Typ GameObject und daher typkompatibel. Wenn nun etwa eine Methode eine Übergabe vom Typ GameObject erwartet, kann sie alle Eigenschaften von GameObject nutzen, also das Attribut name, da ja alle Unterklassen die Eigenschaften erben und Unterklassen die Eigenschaften nicht »wegzaubern« können. Derjenige, dem wir »mehr« übergeben, kann zwar nichts mit den Erweiterungen anfangen, ablehnen wird er das Objekt aber nicht, weil es alle geforderten Eigenschaften aufweist.

Abbildung

Weil anstelle eines Objekts auch ein Objekt der Unterklasse auftauchen kann, sprechen wir von Substitution. Das Prinzip wurde von der Professorin Barbara Liskov[145](Die Zeitschrift »Discover« zählt sie zu den 50 wichtigsten Frauen in der Wissenschaft.) formuliert und heißt daher auch liskovsches Substitutionsprinzip.

Die folgende Klasse QuoteNameFromGameObject nutzt diese Eigenschaft. Sie fordert in der Methode quote() irgendein GameObject, von dem bekannt ist, dass es ein Attribut name hat. Im Hauptprogramm kann quote() ein Spieler oder Raum übergeben werden:

Listing 5.67: com/tutego/insel/game/vd/QuoteNameFromGameObject.java, QuoteNameFromGameObject

public class QuoteNameFromGameObject
{
public static void quote( GameObject go )
{
System.out.println( "'" + go.name + "'" );
}

public static void main( String[] args )
{
GameObject player = new Player();
player.name = "Godman";
quote( player ); // 'Godman'

GameObject room = new Room();
room.name = "Hogwurz";
quote( room ); // 'Hogwurz'
}
}

Mit GameObject haben wir eine Basisklasse geschaffen, die verschiedenen Unterklassen Grundfunktionalität beibringt, in unserem Fall das Attribut name. So liefert die Basisklasse einen gemeinsamen Nenner, etwa gemeinsame Attribute oder Methoden, die jede Unterklasse besitzen wird.

In der Java-Bibliothek finden sich zahllose weitere Beispiele. Die println(Object)-Methode ist so ein Beispiel. Die Methode nimmt beliebige Objekte entgegen, denn der Parametertyp ist Object. Die Substitution besagt, dass wir alle Objekte dort einsetzen können, da alle Klassen von Object abgeleitet sind.


Galileo Computing - Zum Seitenanfang

5.9.3 Typen mit dem instanceof-Operator testenZur nächsten ÜberschriftZur vorigen Überschrift

Der relationale Operator instanceof hilft dabei, Exemplare auf ihre Verwandtschaft mit einem Referenztyp zu prüfen. Er stellt zur Laufzeit fest, ob eine Referenz ungleich null und von einem bestimmten Typ ist. Der Operator ist binär, hat also zwei Operanden:

Listing 5.68: com/tutego/insel/game/vd/InstanceofDemo.java, main()

System.out.println( "Toll" instanceof String );       // true
System.out.println( "Toll" instanceof Object ); // true
System.out.println( new Player() instanceof Object ); // true

Alles in doppelten Anführungsstrichen ist ein String, sodass instanceof String wahr ergibt. Für den zweiten und dritten Fall gilt: Alle Objekte gehen irgendwie aus Object hervor und sind somit logischerweise Erweiterungen.

Hinweis

Der Operator instanceof testet ein Objekt auf seine Hierarchie. So ist zum Beispiel
o instanceof Object
für jedes Objekt o wahr, denn jedes Objekt ist immer Kind von java.lang.Object. Die Programmiersprache Smalltalk unterscheidet hier mit zwei Nachrichten isMemberOf (exakt) und isKindOf (wie Javas instanceof). Um den exakten Typ zu testen, lässt sich mit dem Class-Objekt arbeiten, etwa wie im Ausdruck o.getClass().equals(Object.class), der testet, ob o genau ein Object-Objekt ist.

Die bisherigen Beziehungen hätte der Compiler bereits herausfinden können. Vervollständigen wir das, um zu sehen, dass instanceof wirklich zur Laufzeit den Test durchführen muss. In allen Fällen ist das Objekt zur Laufzeit ein Raum:

Room       go1 = new Room();
System.out.println( go1 instanceof Room ); // true
System.out.println( go1 instanceof GameObject ); // true
System.out.println( go1 instanceof Object ); // true

GameObject go2 = new Room();
System.out.println( go2 instanceof Room ); // true
System.out.println( go2 instanceof GameObject ); // true
System.out.println( go2 instanceof Object ); // true
System.out.println( go2 instanceof Player ); // false

Object go3 = new Room();
System.out.println( go3 instanceof Room ); // true
System.out.println( go3 instanceof GameObject ); // true
System.out.println( go3 instanceof Object ); // true
System.out.println( go3 instanceof Player ); // false
System.out.println( go3 instanceof String ); // false

Der Compiler lässt aber nicht alles durch. Liegen zwei Typen überhaupt nicht in der Typhierarchie, lehnt der Compiler den Test ab, da die Vererbungsbeziehungen schon inkompatibel sind:

System.out.println( "Toll" instanceof StringBuilder );
// Fehler Incompatible conditional operand types String and StringBuilder

Der Ausdruck ist falsch, da StringBuilder keine Basisklasse für String ist.

Zum Schluss:

Object ref1 = new int[ 100 ];
System.out.println( ref1 instanceof String );
System.out.println( new int[100] instanceof String ); // Fehler Compilerfehler
Hinweis

Mit instanceof lässt sich der Programmfluss aufgrund der tatsächlichen Typen steuern, etwa mit Anweisungen wie if(reference instanceof Typ) A else B. In der Regel zeigt Kontrolllogik dieser Art aber tendenziell ein Designproblem an und kann oft anders gelöst werden. Das dynamische Binden ist so eine Lösung; sie wird später vorgestellt.

instanceof und null

Ein instanceof-Test mit einer Referenz, die null ist, gibt immer false zurück:

Object ref2 = null;
System.out.println( ref2 instanceof String ); // null

Das leuchtet ein, denn null entspricht ja keinem konkreten Objekt.

Tipp

Da instanceof einen null-Test enthält, sollte statt etwa

if ( s != null && s instanceof String )
immer vereinfacht so geschrieben werden:
if ( s instanceof String )



Ihr Kommentar

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







<< zurück
  Zum Katalog
Zum Katalog: Java ist auch eine Insel





Java ist auch eine Insel
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Java 7 – Mehr als eine Insel





 Java 7 –
 Mehr als eine Insel


Zum Katalog: Android 3






 Android 3


Zum Katalog: Android-Apps entwickeln






 Android-Apps
 entwickeln


Zum Katalog: NetBeans Platform 7






 NetBeans
 Platform 7


Zum Katalog: Einstieg in Eclipse 3.7






 Einstieg in
 Eclipse 3.7


Zum Katalog: Einstieg in Java






 Einstieg
 in Java


Zum Katalog: Einstieg in Java 7






 Einstieg in
 Java 7


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Galileo Press 2011
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