Rheinwerk Computing < openbook >


 
Inhaltsverzeichnis
Materialien
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Arrays und ihre Anwendungen
5 Der Umgang mit Zeichenketten
6 Eigene Klassen schreiben
7 Objektorientierte Beziehungsfragen
8 Ausnahmen müssen sein
9 Geschachtelte Typen
10 Besondere Typen der Java SE
11 Generics<T>
12 Lambda-Ausdrücke und funktionale Programmierung
13 Architektur, Design und angewandte Objektorientierung
14 Java Platform Module System
15 Die Klassenbibliothek
16 Einführung in die nebenläufige Programmierung
17 Einführung in Datenstrukturen und Algorithmen
18 Einführung in grafische Oberflächen
19 Einführung in Dateien und Datenströme
20 Einführung ins Datenbankmanagement mit JDBC
21 Bits und Bytes, Mathematisches und Geld
22 Testen mit JUnit
23 Die Werkzeuge des JDK
A Java SE-Module und Paketübersicht
Stichwortverzeichnis


Download:

- Listings, ca. 2,7 MB


Buch bestellen
Ihre Meinung?



Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom

Einführung, Ausbildung, Praxis
Buch: Java ist auch eine Insel


Java ist auch eine Insel

Pfeil7 Objektorientierte Beziehungsfragen
Pfeil7.1 Assoziationen zwischen Objekten
Pfeil7.1.1 Unidirektionale 1:1-Beziehung
Pfeil7.1.2 Zwei Freunde müsst ihr werden – bidirektionale 1:1-Beziehungen
Pfeil7.1.3 Unidirektionale 1:n-Beziehung
Pfeil7.2 Vererbung
Pfeil7.2.1 Vererbung in Java
Pfeil7.2.2 Spielobjekte modellieren
Pfeil7.2.3 Die implizite Basisklasse java.lang.Object
Pfeil7.2.4 Einfach- und Mehrfachvererbung *
Pfeil7.2.5 Sehen Kinder alles? Die Sichtbarkeit protected
Pfeil7.2.6 Konstruktoren in der Vererbung und super(…)
Pfeil7.3 Typen in Hierarchien
Pfeil7.3.1 Automatische und explizite Typumwandlung
Pfeil7.3.2 Das Substitutionsprinzip
Pfeil7.3.3 Typen mit dem instanceof-Operator testen
Pfeil7.4 Methoden überschreiben
Pfeil7.4.1 Methoden in Unterklassen mit neuem Verhalten ausstatten
Pfeil7.4.2 Mit super an die Eltern
Pfeil7.4.3 Finale Klassen und finale Methoden
Pfeil7.4.4 Kovariante Rückgabetypen
Pfeil7.4.5 Array-Typen und Kovarianz *
Pfeil7.5 Drum prüfe, wer sich dynamisch bindet
Pfeil7.5.1 Gebunden an toString()
Pfeil7.5.2 Implementierung von System.out.println(Object)
Pfeil7.5.3 Nicht dynamisch gebunden bei privaten, statischen und finalen Methoden
Pfeil7.5.4 Dynamisch gebunden auch bei Konstruktoraufrufen *
Pfeil7.5.5 Eine letzte Spielerei mit Javas dynamischer Bindung und überdeckten Attributen *
Pfeil7.6 Abstrakte Klassen und abstrakte Methoden
Pfeil7.6.1 Abstrakte Klassen
Pfeil7.6.2 Abstrakte Methoden
Pfeil7.7 Schnittstellen
Pfeil7.7.1 Schnittstellen sind neue Typen
Pfeil7.7.2 Schnittstellen deklarieren
Pfeil7.7.3 Abstrakte Methoden in Schnittstellen
Pfeil7.7.4 Implementieren von Schnittstellen
Pfeil7.7.5 Ein Polymorphie-Beispiel mit Schnittstellen
Pfeil7.7.6 Die Mehrfachvererbung bei Schnittstellen
Pfeil7.7.7 Keine Kollisionsgefahr bei Mehrfachvererbung *
Pfeil7.7.8 Erweitern von Interfaces – Subinterfaces
Pfeil7.7.9 Konstantendeklarationen bei Schnittstellen
Pfeil7.7.10 Nachträgliches Implementieren von Schnittstellen *
Pfeil7.7.11 Statische ausprogrammierte Methoden in Schnittstellen
Pfeil7.7.12 Erweitern und Ändern von Schnittstellen
Pfeil7.7.13 Default-Methoden
Pfeil7.7.14 Erweiterte Schnittstellen deklarieren und nutzen
Pfeil7.7.15 Öffentliche und private Schnittstellenmethoden
Pfeil7.7.16 Erweiterte Schnittstellen, Mehrfachvererbung und Mehrdeutigkeiten *
Pfeil7.7.17 Bausteine bilden mit Default-Methoden *
Pfeil7.7.18 Initialisierung von Schnittstellenkonstanten *
Pfeil7.7.19 Markierungsschnittstellen *
Pfeil7.7.20 (Abstrakte) Klassen und Schnittstellen im Vergleich
Pfeil7.8 SOLIDe Modellierung
Pfeil7.8.1 DRY, KISS und YAGNI
Pfeil7.8.2 SOLID
Pfeil7.8.3 Sei nicht STUPID
Pfeil7.9 Zum Weiterlesen
 

Zum Seitenanfang

7    Objektorientierte Beziehungsfragen Zur vorigen ÜberschriftZur nächsten Überschrift

»Aus einer schlechten Verbindung kann man sich schwerer lösen als aus einer guten.«

– Whitney Elizabeth Houston (1963–2012)

Objekte leben nicht in Isolation, sondern in Beziehungen zu anderen Objekten. Was wir uns in diesem Kapitel anschauen wollen, sind die Objektbeziehungen und Typbeziehungen, die Objekte und Klassen/Schnittstellen eingehen können. Im Grunde läuft das auf zwei einfache Beziehungstypen hinaus: Ein Objekt ist mit einem anderen Objekt über eine Referenz verbunden, oder eine Klasse erbt von einer anderen Klasse, sodass die Objekte Eigenschaften von der Oberklasse erben können. Insofern betrachtet dieses Kapitel Assoziationen für die Objektverbindungen und Vererbungsbeziehungen. Darüber hinaus geht das Kapitel auf abstrakte Klassen und Schnittstellen ein, die besondere Vererbungsbeziehungen darstellen, da sie für die Unterklassen Verhalten vorschreiben können.

 

Zum Seitenanfang

7.1    Assoziationen zwischen Objekten Zur vorigen ÜberschriftZur nächsten Überschrift

Eine wichtige Eigenschaft objektorientierter Systeme ist der Austausch von Nachrichten untereinander. Dazu »kennt« ein Objekt andere Objekte und kann Anforderungen weitergeben. Diese Verbindung nennt sich Assoziation und ist das wichtigste Werkzeug bei der Konstruktion von Objektverbänden.

Assoziationstypen

Bei Assoziationen ist zu unterscheiden, ob nur eine Seite die andere kennt oder ob eine Navigation in beiden Richtungen möglich ist:

  • Eine unidirektionale Beziehung geht nur in eine Richtung (ein Fan kennt seine Band, aber nicht umgekehrt).

  • Eine bidirektionale Beziehung geht in beide Richtungen (Raum kennt Spieler, und Spieler kennt Raum). Eine bidirektionale Beziehung ist natürlich ein großer Vorteil, da die Anwendung die Assoziation in beliebiger Richtung ablaufen kann.

Daneben gibt es bei Beziehungen die Multiplizität, auch Kardinalität genannt. Sie sagt aus, mit wie vielen Objekten eine Seite eine Beziehung hat oder haben kann. Übliche Beziehungen sind 1:1 und 1:n. Weiterhin können wir beschreiben, ob ein Teil existenzabhängig ist oder alleine existieren kann.

 

Zum Seitenanfang

7.1.1    Unidirektionale 1:1-Beziehung Zur vorigen ÜberschriftZur nächsten Überschrift

Damit ein Spieler sich in einem Raum befinden kann, lässt sich in Player eine Referenzvariable vom Typ Room anlegen. In Java sieht das so aus:

Listing 7.1    src/main/java/com/tutego/insel/game/va/Player.java, Player

class Player {

Room room;

}

Listing 7.2    src/main/java/com/tutego/insel/game/va/Room.java, Room

class Room { }

Zur Laufzeit müssen natürlich noch die Verweise gesetzt werden:

Listing 7.3    src/main/java/com/tutego/insel/game/va/Playground.java, main()

Player buster = new Player();

Room tower = new Room();

buster.room = tower; // Buster kommt in den Tower

Assoziationen in der UML

Die UML stellt Assoziationen durch eine Linie zwischen den beteiligten Klassen dar. Hat eine Assoziation eine Richtung, zeigt ein Pfeil am Ende der Assoziation diese an (siehe Abbildung 7.1). Wenn es keine Pfeile gibt, heißt das nur, dass die Richtung noch nicht genauer spezifiziert ist. Es bedeutet nicht automatisch, dass die Beziehung bidirektional ist.

Gerichtete Assoziation im UML-Diagramm

Abbildung 7.1    Gerichtete Assoziation im UML-Diagramm

Die Multiplizität wird angegeben als »untere Grenze..obere Grenze«, etwa 1..4. Außerdem lässt sich in UML über eine Rolle angeben, welche Aufgabe die Beziehung für eine Seite hat. Die Rollen sind wichtig für reflexive Assoziationen (auch zirkuläre oder rekursive Assoziationen genannt), wenn ein Typ auf sich selbst zeigt. Ein beliebtes Beispiel ist der Typ Person mit den Rollen Chef und Mitarbeiter.

 

Zum Seitenanfang

7.1.2    Zwei Freunde müsst ihr werden – bidirektionale 1:1-Beziehungen Zur vorigen ÜberschriftZur nächsten Überschrift

Gerichtete Assoziationen sind in Java sehr einfach umzusetzen, wie wir im Beispiel gesehen haben. Beidseitige Assoziationen erscheinen auf den ersten Blick auch einfach, da nur die Gegenseite um eine Verweisvariable erweitert werden muss. Beginnen wir mit dem Szenario, dass der Spieler seinen Raum und der Raum seinen Spieler kennen soll (siehe Abbildung 7.2):

Listing 7.4    src/main/java/com/tutego/insel/game/vb/Player.java, Player

class Player {

Room room;

}

Listing 7.5    src/main/java/com/tutego/insel/game/vb/Room.java, Room

class Room {

Player player;

}
Bei bidirektionalen Beziehungen gibt es im UML-Diagramm zwei Pfeilspitzen.

Abbildung 7.2    Bei bidirektionalen Beziehungen gibt es im UML-Diagramm zwei Pfeilspitzen.

Verbinden wir das (siehe Abbildung 7.3):

Listing 7.6    src/main/java/com/tutego/insel/game/vb/Playground.java, main()

Player buster = new Player();

Room tower = new Room();

buster.room = tower;

tower.player = buster;
Bei bidirektionalen Beziehungen kommen wir von jeder Seite zur anderen Seite.

Abbildung 7.3    Bei bidirektionalen Beziehungen kommen wir von jeder Seite zur anderen Seite.

Korrektheit der Verbindungen sicherstellen

So einfach ist es aber nicht! Bidirektionale Beziehungen erfordern etwas mehr Programmieraufwand, da sichergestellt sein muss, dass beide Seiten eine gültige Referenz besitzen. Denn wird die Assoziation auf einer Seite aufgekündigt, etwa durch Setzen der Referenz auf null, muss auch die andere Seite die Referenz lösen:

buster.room = null;        // Spieler will nicht mehr im Raum sein

Auch kann es passieren, dass zwei Räume angeben, einen Spieler zu besitzen, doch der Spieler kennt von der Modellierung her nur genau einen Raum:

Listing 7.7    src/main/java/com/tutego/insel/game/vb/InvalidPlayground.java, main()

Player buster = new Player();

Room tower = new Room();

buster.room = tower;

tower.player = buster;

Room toilet = new Room();

toilet.player = buster;

System.out.println( buster ); // com.tutego.insel.game.vb.Player@aaaaaa

System.out.println( tower ); // com.tutego.insel.game.vb.Room@444444

System.out.println( toilet ); // com.tutego.insel.game.vb.Room@999999

System.out.println( buster.room ); // com.tutego.insel.game.vb.Room@444444

System.out.println( tower.player ); // com.tutego.insel.game.vb.Player@aaaaaa

System.out.println( toilet.player ); // com.tutego.insel.game.vb.Player@aaaaaa

An der Ausgabe ist abzulesen, dass sich Buster im Tower befindet, aber auch die Toilette sagt, dass Buster dort ist. (Die Kennungen hinter @ sind für das Buch durch gut unterscheidbare Zeichenketten ersetzt worden. Sie sind bei jedem Aufruf anders.)

Die Wurzel des Übels liegt in den Variablen. Variablen können keine Konsistenzbedingungen aufrechterhalten, Methoden können wie in einer Transaktion aber mehrere Operationen durchführen und von einem korrekten Zustand in den nächsten überführen. Daher erfolgt diese Kontrolle am besten mit Zugriffsmethoden, etwa setRoom(…) und setPlayer(…).

 

Zum Seitenanfang

7.1.3    Unidirektionale 1:n-Beziehung Zur vorigen ÜberschriftZur nächsten Überschrift

Immer dann, wenn ein Objekt mehrere andere Objekte referenzieren muss, reicht eine einfache Referenzvariable vom Typ der anderen Seite nicht mehr aus. Es gibt mehrere Ansätze, dass ein Objekt mehr als ein anderes Objekt referenziert.

Beziehung zu einer kleinen überschaubaren Anzahl von Objekten

Ist die Anzahl der assoziierten Objekte fix und überschaubar, dann lassen sich mehrere Variablen verwenden.

[zB]  Beispiel

Ein Raum hat maximal zwei Spieler:

class Room {

Player player1, player2;

}

Ein Raum hat verbundene Räume in alle vier Himmelsrichtungen:

class Room {

Room north;

Room west;

Room east;

Room south;

}

Hier steckt indirekt eine bidirektionale Beziehung drin.

Datenstrukturen (Container)

Soll ein Objekt mehr als eine feste Anzahl Referenzen aufnehmen, ist die Lösung über mehrere Variablen nicht mehr tragbar – vor allem dann, wenn die referenzierten Objekte keine Sonderstellung haben. Es ergibt sicherlich Sinn, bei einer Person den rechten und den linken Arm getrennt zu betrachten, aber bei 16 Räumen der Art north, northnorthwest, northwest, westnorthwest, west, westsouthwest, southwest wird es langsam unübersichtlich.

Wenn sich etwa in einem Raum mehrere Spieler befinden oder wenn ein Spieler eine beliebige Anzahl Gegenstände mit sich trägt, sind Datenstrukturen gefragt. Wir verwenden auf der 1-Seite einen speziellen Container, der für uns die Referenzen speichert. Das ist ein zentraler Schritt, denn wir geben die Verantwortung, wer die Referenzen speichert, ab.

Beziehung zu einer großen bekannten Anzahl von Objekten

Eine Handy-Tastatur hat eine feste Anzahl von Tasten und ein Tisch eine feste Anzahl von Beinen. Bei Sammlungen dieser Art ist ein Array gut geeignet. Bei anderen Beziehungen, wo die Anzahl referenzierter Objekte dynamisch ist, ist ein Array wenig elegant, da die manuellen Vergrößerungen oder Verkleinerungen mühevoll sind.

Der Raum übernimmt die Speicherung oder ein Array.

Abbildung 7.4    Der Raum übernimmt die Speicherung oder ein Array.

Beziehung zu einer unbekannten Anzahl von Objekten mit der dynamischen Datenstruktur ArrayList

Wollen wir zum Beispiel erlauben, dass ein Spieler mehrere Gegenstände tragen kann oder dass sich eine unbekannte Anzahl Spieler in einem Raum befinden können, ist eine dynamische Datenstruktur wie java.util.ArrayList sinnvoller. Genauer wollen wir uns zwar erst in Kapitel 17, »Einführung in Datenstrukturen und Algorithmen«, mit besagten Datenstrukturen und Algorithmen beschäftigen, doch seien an dieser Stelle schon drei Methoden der ArrayList vorgestellt, die Elemente in einer Liste (Sequenz) hält:

  • boolean add( E o ) fügt ein Objekt vom Typ E der Liste hinzu.

  • int size() liefert die Anzahl der Elemente in der Liste.

  • E get( int index ) liefert das Element an der Stelle index.

Ein Raum mit vielen Spielern

Mit diesem Wissen wollen wir dem Raum Methoden geben, sodass er beliebig viele Spieler aufnehmen kann. Für den unidirektionalen Fall ist die Player-Klasse wieder einfach:

Listing 7.8    src/main/java/com/tutego/insel/game/vc/Player.java, Player

public class Player {

public String name;



public Player( String name ) {

this.name = name;

}

}

Vorher hat der Raum nur eine Referenzvariable vom Typ Player gehabt, die nur genau einen Spieler referenzieren konnte. Mit dem Einsatz von Datenstrukturen bleibt das sogar gleich: Wir referenzieren einen Container im Raum, und dieser Container verwaltet für uns die Player.

Um von nur einem Spieler zu einer Sammlung von Spielern zu kommen, ändern wir

Player player;

in:

ArrayList<Player> players = new ArrayList<Player>();

Der Raum bekommt ein internes Attribut players vom Typ der ArrayList. Dass Angaben in spitzen Klammern hinter dem Typ stehen, liegt an den Java-Generics – sie besagen, dass die ArrayList nur Player aufnehmen wird und keine anderen Dinge (wie Geister).[ 156 ](Die Schreibweise lässt sich auch noch ein wenig abkürzen zu ArrayList<Player> players =  new ArrayList<>();, doch das ist jetzt nicht wichtig. )

Die Details zu Generics sind Thema von Kapitel 11, »Generics<T>«, doch unser Wissen ist an dieser Stelle ausreichend, um die Raum-Klasse fertigzustellen (siehe Abbildung 7.5):

Listing 7.9    src/main/java/com/tutego/insel/game/vc/Room.java, Room

import java.util.ArrayList;



public class Room {



private ArrayList<Player> players = new ArrayList<Player>();



public void addPlayer( Player player ) {

players.add( player );

}



public void listPlayers() {

for ( Player player : players )

System.out.println( player.name );

}

}
UML-Diagramm, bei dem der »Room« beliebig viele »Player« referenziert

Abbildung 7.5    UML-Diagramm, bei dem der »Room« beliebig viele »Player« referenziert

Die Datenstruktur selbst ist privat, und die addPlayer(…)-Methode fügt einen Spieler in die ArrayList ein. Eine Besonderheit bietet die Methode listPlayers(), denn sie nutzt das erweiterte for zum Durchlaufen aller Spieler. Beim erweiterten for ist rechts vom Doppelpunkt nicht nur ein Array erlaubt, sondern auch eine Datenstruktur wie die Liste. Nachdem also zwei Spieler mit addPlayer(…) hinzugefügt wurden, wird listPlayers() die beiden Spielernamen ausgeben:

Listing 7.10    src/main/java/com/tutego/insel/game/vc/Playground.java, main()

Room oceanLiner = new Room();

oceanLiner.addPlayer( new Player( "Tim" ) );

oceanLiner.addPlayer( new Player( "Jorry" ) );

oceanLiner.listPlayers(); // Tim Jorry
[»]  Schnelleinstieg in Generics

Java ist eine typisierte Programmiersprache, was bedeutet, dass jede Variable und jeder Ausdruck einen Typ hat, den der Compiler kennt und der sich zur Laufzeit nicht ändert. Eine Zählvariable ist zum Beispiel vom Typ int, ein Abstand zwischen zwei Punkten ist vom Typ double, und ein Koordinatenpaar ist vom Typ Point. Allerdings gibt es bei der Typisierung Lücken. Nehmen wir etwa eine Liste von Punkten:

List list;

Zwar ist die Variable list nun mit List typisiert, und das ist besser als nichts, jedoch bleibt unklar, was die Liste eigentlich genau für Objekte speichert. Sind es Punkte, Einhörner oder rostige Fähren? Es wäre sinnvoll, nicht nur die Liste selbst als Typ zu haben, sondern sozusagen rekursiv in die Liste hineinzugehen und genau hinzuschauen, was die Liste eigentlich referenziert. Genau das ist die Aufgabe von Generics. Die Datenstruktur wünscht sich eine Typangabe, was sie genau speichert. Dieser Typ erscheint in spitzen Klammern hinter dem eigentlichen »Haupttyp«.

List<Point> list;

Mit Generics haben API-Designer ein Werkzeug, um Typen noch genauer vorzuschreiben. Die Entwickler des Typs List können so vom Nutzer fordern, den Elementtyp anzugeben. So können Entwickler dem Compiler genauer sagen, was sie für Typen verwenden, und es dem Compiler ermöglichen, genauere Tests durchzuführen. Es ist erlaubt und möglich, diesen »Nebentyp« nicht anzugeben, doch das führt zu einer Compilerwarnung und ist nicht empfehlenswert: Je genauer Typangaben sind, desto besser ist das für alle.

Vereinzelt kommen in den nächsten Kapiteln generische Typen vor, etwa Comparable (hilft, Objekte zu vergleichen). An dieser Stelle reicht es, zu verstehen, dass wir als Nutzer einen Typ in spitze Klammern eintragen müssen. Mit Generics selbst beschäftigen wir uns in Kapitel 11, »Generics<T>«, genauer.

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java ist auch eine Insel Java ist auch eine Insel

Jetzt Buch bestellen


 Buchempfehlungen
Zum Rheinwerk-Shop: Captain CiaoCiao erobert Java

Captain CiaoCiao erobert Java




Zum Rheinwerk-Shop: Java SE 9 Standard-Bibliothek

Java SE 9 Standard-Bibliothek




Zum Rheinwerk-Shop: Algorithmen in Java

Algorithmen in Java




Zum Rheinwerk-Shop: Objektorientierte Programmierung

Objektorientierte Programmierung




 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2021

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.

 

[Rheinwerk Computing]



Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de



Cookie-Einstellungen ändern