Rheinwerk Computing < openbook >


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


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

Pfeil 7 Objektorientierte Beziehungsfragen
Pfeil 7.1 Assoziationen zwischen Objekten
Pfeil 7.1.1 Unidirektionale 1:1-Beziehung
Pfeil 7.1.2 Zwei Freunde müsst ihr werden – bidirektionale 1:1-Beziehungen
Pfeil 7.1.3 Unidirektionale 1:n-Beziehung
Pfeil 7.2 Vererbung
Pfeil 7.2.1 Vererbung in Java
Pfeil 7.2.2 Ereignisse modellieren
Pfeil 7.2.3 Die implizite Basisklasse java.lang.Object
Pfeil 7.2.4 Einfach- und Mehrfachvererbung *
Pfeil 7.2.5 Sehen Kinder alles? Die Sichtbarkeit protected
Pfeil 7.2.6 Konstruktoren in der Vererbung und super(…)
Pfeil 7.3 Typen in Hierarchien
Pfeil 7.3.1 Automatische und explizite Typumwandlung
Pfeil 7.3.2 Das Substitutionsprinzip
Pfeil 7.3.3 Typen mit dem instanceof-Operator testen
Pfeil 7.3.4 Pattern-Matching bei instanceof
Pfeil 7.4 Methoden überschreiben
Pfeil 7.4.1 Methoden in Unterklassen mit neuem Verhalten ausstatten
Pfeil 7.4.2 Mit super an die Eltern
Pfeil 7.5 Drum prüfe, wer sich dynamisch bindet
Pfeil 7.5.1 Gebunden an toString()
Pfeil 7.5.2 Implementierung von System.out.println(Object)
Pfeil 7.6 Finale Klassen und finale Methoden
Pfeil 7.6.1 Finale Klassen
Pfeil 7.6.2 Nicht überschreibbare (finale) Methoden
Pfeil 7.7 Abstrakte Klassen und abstrakte Methoden
Pfeil 7.7.1 Abstrakte Klassen
Pfeil 7.7.2 Abstrakte Methoden
Pfeil 7.8 Weiteres zum Überschreiben und dynamischen Binden
Pfeil 7.8.1 Nicht dynamisch gebunden bei privaten, statischen und finalen Methoden
Pfeil 7.8.2 Kovariante Rückgabetypen
Pfeil 7.8.3 Array-Typen und Kovarianz *
Pfeil 7.8.4 Dynamisch gebunden auch bei Konstruktoraufrufen *
Pfeil 7.8.5 Keine dynamische Bindung bei überdeckten Objektvariablen *
Pfeil 7.9 Zum Weiterlesen und Programmieraufgabe
 

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 (eine Person hat eine Adresse, aber die Adresse »kennt« die Person nicht).

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

Unidirektionale Beziehung

Abbildung 7.1     Unidirektionale Beziehung

Daneben gibt es bei Beziehungen die Multiplizität, die aussagt, mit wie vielen Objekten eine Seite eine Beziehung hat oder haben kann. Sie entspricht bei Datenbanken der Kardinalität. Ü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

In diesem Abschnitt wollen wir eine neue Klasse einführen für einen Spieler (Player); er soll sich in einer Stadt befinden und auch zu einer neuen Stadt reisen können.

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

class Player {

private City location;

void travelTo( City newLocation ) { this.location = newLocation; }

City currentLocation() { return location; }

}

Damit ein Spieler den aktuellen Aufenthaltsort referenzieren kann, besitzt die Klasse Player eine Objektvariable vom Typ City. Die Stadt hat lediglich einen Namen:

Listing 7.2     src/main/java/com/tutego/insel/game/c/vi/City.java, Player

class City {

private final String name;

public City( String name ) { this.name = name; }

@Override public String toString() { return name; }

}

Zur Laufzeit müssen die Verweise gesetzt werden:

Listing 7.3     src/main/java/com/tutego/insel/game/c/vi/Application.java, main

City candyTown  = new City( "Candy Town" );

Player pillLady = new Player();

pillLady.travelTo( candyTown );

System.out.println( pillLady.currentLocation() ); // Candy Town

pillLady.travelTo( new City( "Riesen City" ) );

System.out.println( pillLady.currentLocation() ); // Riesen City

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

Assoziation über Attribute im UML-Diagramm

Abbildung 7.2     Assoziation über Attribute im UML-Diagramm

Gerichtete Assoziation im UML-Diagramm

Abbildung 7.3     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 eine Süßigkeit hat und die Süßigkeit »weiß«, welcher Spieler gerade an ihr leckt (siehe Abbildung 7.3):

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

class Player {

Candy candy;

}

Listing 7.5     src/main/java/com/tutego/insel/game/c/vj/Candy.java, Candy

class Candy {

Player player;

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

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

Verbinden wir das (siehe Abbildung 7.4):

Listing 7.6     src/main/java/com/tutego/insel/game/c/vj/Application.java, Ausschnitt

Candy cottonCandy  = new Candy();

Player pillLady = new Player();

pillLady.candy = cottonCandy;

cottonCandy.player = pillLady;

Korrektheit der Verbindungen sicherstellen

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:

player.candy = null;        // Spieler will nichts mehr Süßes

Wenn der Spieler eine neue Süßigkeit wählt, dann muss die alte Süßigkeit ebenfalls den Spieler »vergessen« und auch umgekehrt: Wenn die Süßigkeit den Spieler wechselt, so muss auch der alte Spieler die Süßigkeit streichen.

Listing 7.7     src/main/java/com/tutego/insel/game/c/vj/Application.java, Ausschnitt

Candy cottonCandy  = new Candy();

Player pillLady = new Player();

pillLady.candy = cottonCandy;

cottonCandy.player = pillLady;



// Player leckt etwas Neues

Candy babyRuth = new Candy();

pillLady.candy = babyRuth;

Hier haben wir genau das Problem, dass die Spielerin sich für eine neue Süßigkeit entschieden hat, aber würden wir die Süßigkeit cottonCandy fragen, würde sie immer noch auf pillLady zeigen – aber das ist bei 1:1-Beziehungen nicht gewünscht.

Die Wurzel des Übels liegt in den direkt zugänglichen Variablen. Variablen können keine Konsistenzbedingungen aufrechterhalten – wir müssen dies über Methoden realisieren. Methoden können wie in einer Transaktion mehrere Operationen durchführen und von einem korrekten Zustand in den nächsten überführen. Daher erfolgt das Lösen oder Neusetzen von Beziehungen am besten mit Methoden, etwa setCandy(…) 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 Spieler hat in seiner linken und rechten Tasche eine Süßigkeit:

class Player {

Candy candyLeftPocket, candyRightPocket;

}

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 hat ein Spieler 10 Taschen für Süßigkeiten, sollten die Variablen sicherlich nicht pocketCandy1, pocketCandy2, pocketCandy3 … heißen.

Datenstrukturen (Container)

Wenn etwa ein Spieler eine beliebige Anzahl von Süßigkeiten 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, vor allen Dingen, weil die Anzahl der Elemente vorher bekannt sind und sich die Elemente über einen Index ansprechen lassen.

[zB]  Beispiel

Ein Spieler kann 10 Süßigkeiten speichern:

class Player {

Candy[] candies = new Candy[ 10 ];

}

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 Spieler übernimmt die Speicherung oder ein Array.

Abbildung 7.5     Der Spieler ü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 Süßigkeiten tragen kann oder dass sich in einer Stadt eine beliebige Anzahl Spieler aufhalten kann, sind Arrays unpraktisch, und eine dynamische Datenstruktur wie java.util.ArrayList ist sinnvoller. Genauer wollen wir uns zwar erst in Kapitel 18, »Einführung in Datenstrukturen und Algorithmen«, mit besagten Datenstrukturen und Algorithmen beschäftigen, doch seien an dieser Stelle schon vier Methoden der ArrayList vorgestellt, die Elemente vom Typ E in einer Liste (Sequenz) hält:

  • boolean add(E e) fügt ein Objekt e der Liste hinzu.

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

  • E remove(int index) löscht das Element an der Stelle index.

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

Weiterhin kann eine List rechts vom Doppelpunkt der erweiterten for-Schleife stehen, sodass die Liste einfach abgelaufen werden kann.

Ein Spieler ist hungrig auf viele Süßigkeiten

Hat ein Spieler nur eine Süßigkeit, sieht das im Code so aus:

class Player {

Candy candy;

}

Mit einer Datenstruktur kann der Spieler leicht mehrere Süßigkeiten speichern:

class Player {

ArrayList candies = new ArrayList();

}

Vorher hatte der Spieler eine Referenzvariable vom Typ Candy, jetzt eine Referenzvariable vom Typ ArrayList. Interessanterweise gibt es keine direkte 1:n-Beziehung im Player, denn der Spieler speichert weiterhin nur eine Referenz, nur ist diese Referenz nicht mehr auf ein Candy, sondern auf einen Container, und der eine Container speichert für uns beliebig viele Süßigkeiten.

[»]  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 Süßigkeiten:

List candies;

Zwar ist die Variable candies nun mit List typisiert, und das ist besser als nichts, jedoch bleibt unklar, was die Liste eigentlich genau für Objekte speichert. Sind es Süßigkeiten, Einhörner oder rostige Fähren? Es wäre sinnvoll, nicht nur die Liste selbst als Typ zu haben, sondern sozusagen in die Liste hineinzugehen und genau hinzuschauen, was die Liste eigentlich referenziert. Genau das ist die Aufgabe von Generics. Eine ArrayList ist ein sogenannter generischer Typ und kann eine weitere Typinformation tragen, die in spitzen Klammern hinter den Typnamen, dem eigentlichen »Haupttyp«, gesetzt wird:

ArrayList<Candy> candies = new ArrayList<Candy>();

Die Angaben in spitzen Klammern besagen, dass die ArrayList nur Candy-Instanzen aufnehmen soll und keine anderen Dinge wie Wurstwaren oder Impfpässe.[ 157 ](Die Schreibweise lässt sich auch noch ein wenig abkürzen zu ArrayList<Candy> candies =  new ArrayList<>();, doch das ist jetzt nicht wichtig. )

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. Die Details zu Generics sind Thema von Kapitel 12, »Generics<T>«, doch unser Wissen ist an dieser Stelle ausreichend, damit der Spieler korrekt eine beliebige Anzahl Süßigkeiten speichern kann.

Bauen wir das Programm ein bisschen aus. Der Spieler soll Süßigkeiten kaufen und essen können. Zunächst zur Deklaration von Candy, die einen Namen und eine Kalorienanzahl hat:

Listing 7.8     src/main/java/com/tutego/insel/game/c/vk/Candy.java, Candy

public class Candy {

public final String name;

public final int calories;

public Candy( String name, int calories ) {

this.name = Objects.requireNonNull( name );

this.calories = calories;

}

@Override public String toString() { return name + ", " + calories; }

}

Der Spieler kann eine Süßigkeit kaufen, sie essen und eine Liste aller Süßigkeiten ausgeben; die drei Methoden greifen alle auf die private Variable candies zurück:

Listing 7.9     src/main/java/com/tutego/insel/game/c/vk/Player.java, Player

public class Player {

private final ArrayList<Candy> candies = new ArrayList<Candy>();



public void buy( Candy newCandy ) {

candies.add( Objects.requireNonNull( newCandy ) );

}



public boolean eat( String name ) {

for ( int i = 0; i < candies.size(); i++ )

if ( candies.get( i ).name.equals( name ) ) {

candies.remove( i );

return true;

}

return false;

}



public void listCandies() {

int sum = 0;

for ( Candy candy : candies ) {

System.out.println( candy );

sum += candy.calories;

}

System.out.printf( "Summe aller Kalorien: %d%n", sum );

}

}
UML-Diagramm, bei dem der »Spieler« beliebig viele Süßigkeiten referenziert

Abbildung 7.6     UML-Diagramm, bei dem der »Spieler« beliebig viele Süßigkeiten referenziert

Die Datenstruktur selbst ist privat, und von außen ist kein Zugriff möglich. Nur die Methoden greifen auf die Datenstruktur zu. Zu den drei Methoden:

  • buy(Candy) fügt eine neue Süßigkeit in die ArrayList ein. Objects.requireNonNull(…) führt zu einer NullPointerException, wenn fälschlicherweise buy(null) aufgerufen wird, denn null soll nicht in die Liste.

  • Die Methode eat(String) läuft mit einem Index über die Liste, sucht die Liste ab, ob eine Süßigkeit mit dem gleichen Namen vorkommt, und löscht mit der Listen-Methode remove(int) an dieser Stelle die Süßigkeit. Konnte eine Süßigkeit gefunden und gelöscht werden, liefert eat(…) die Rückgabe true, andernfalls false.

  • Ein schönes Sprachfeature nutzt listCandies(): die erweiterte for-Schleife zum Durchlaufen aller Süßigkeiten in der Liste. Bei der erweiterten for-Schleife ist rechts vom Doppelpunkt nicht nur ein Array erlaubt, sondern auch eine Datenstruktur wie die ArrayList. Im Rumpf unserer Schleife wird von jedem Candy-Objekt die toString()-Repräsentation auf dem Bildschirm ausgegeben, die Kalorien extrahiert und summiert. Nach der Schleife werden die Gesamtkalorien ebenfalls auf dem Bildschirm ausgegeben.

Ein kleines Beispielprogramm:

Listing 7.10     src/main/java/com/tutego/insel/game/c/vk/Application.java, main

Player reese = new Player();

reese.buy( new Candy( "Kitty Katty", 200 ) );

reese.buy( new Candy( "P&Ps", 250 ) );

reese.buy( new Candy( "Snackers", 300 ) );

reese.buy( new Candy( "Snackers", 300 ) );

reese.buy( new Candy( "Bubba Hubba", 350 ) );

reese.listCandies();

System.out.println( reese.eat( "Chick-o-Stick" ) );

System.out.println( reese.eat( "Kitty Katty" ) );

reese.listCandies();

reese.buy( new Candy( "Kitty Katty", 200 ) );

reese.buy( new Candy( "Kitty Katty", 200 ) );

System.out.println( reese.eat( "Kitty Katty" ) );

reese.listCandies();

Ein Programmdurchlauf gibt folgende Bildschirmausgabe:

Kitty Katty, 200

P&Ps, 250

Snackers, 300

Snackers, 300

Bubba Hubba, 350

Summe aller Kalorien: 1400

false

true

P&Ps, 250

Snackers, 300

Snackers, 300

Bubba Hubba, 350

Summe aller Kalorien: 1200

true

P&Ps, 250

Snackers, 300

Snackers, 300

Bubba Hubba, 350

Kitty Katty, 200

Summe aller Kalorien: 1400

 


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: Algorithmen in Java

Algorithmen in Java




Zum Rheinwerk-Shop: Spring Boot 3 und Spring Framework 6

Spring Boot 3 und Spring Framework 6




Zum Rheinwerk-Shop: Java SE 9 Standard-Bibliothek

Java SE 9 Standard-Bibliothek




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

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2024

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