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.8    Weiteres zum Überschreiben und dynamischen Binden Zur vorigen ÜberschriftZur nächsten Überschrift

 

Zum Seitenanfang

7.8.1    Nicht dynamisch gebunden bei privaten, statischen und finalen Methoden Zur vorigen ÜberschriftZur nächsten Überschrift

Ist eine Methode privat, statisch oder final, wird sie nicht überschrieben, und solche Methoden nehmen nicht an der dynamischen Bindung teil. Für den Compiler ist es in Ordnung, wenn es eine Methode in der Unterklasse gibt, die den gleichen Namen wie eine private Methode in der Oberklasse trägt. Das ist auch gut so, denn private Implementierungen sind ja ohnehin geheim und versteckt. Die Unterklasse soll von den privaten Methoden in der Oberklasse gar nichts wissen. Statt von Überschreiben sprechen wir hier von Überdecken oder Verdecken.

Dass private, statische und finale Methoden nicht überschrieben werden, ist ein wichtiger Beitrag zur Sicherheit. Falls nämlich Unterklassen interne private Methoden überschreiben könnten, wäre dies eine Verletzung der inneren Arbeitsweise der Oberklasse. In einem Satz: Private Methoden sind nicht in den Unterklassen sichtbar und werden daher nicht überschrieben. Andernfalls könnten private Implementierungen im Nachhinein geändert werden, und Oberklassen wären nicht mehr sicher, dass nur ihre eigenen Methoden benutzt werden.

 

Zum Seitenanfang

7.8.2    Kovariante Rückgabetypen Zur vorigen ÜberschriftZur nächsten Überschrift

Überschreibt eine Methode mit einem Referenztyp als Rückgabe eine andere, so kann die überschreibende Methode als Rückgabetyp irgendeinen Untertyp des Rückgabetyps der überschriebenen Methode nutzen. Die Erklärung klingt komplizierter, als es ist, daher ein kurzes Beispiel:

Listing 7.52     src/main/java/com/tutego/insel/game/c/vr/Application.java, main

class Event {}

class Workout extends Event {}



class Calendar {

Event first() { return new Event(); }

}



class WorkoutCalendar extends Calendar {

// @Override Event first() {

@Override Workout first() {

return new Workout();

}

}

Die Klasse Calendar deklariert eine Methode first() und liefert ein Event. Die Calendar-Unterklasse WorkoutCalendar überschreibt die Methode first() und könnte natürlich den Rückgabetyp Event nutzen, doch liefert sie ein Untertyp von Event, und zwar Workout. Diese Möglichkeit nennt sich kovarianter Rückgabetyp und ist sehr praktisch, da sich auf diese Weise Entwickler oft explizite Typumwandlung sparen können.

[»]  Hinweis

Merkwürdig ist in diesem Zusammenhang, dass es in Java schon immer veränderte Zugriffsrechte gegeben hat. Eine Unterklasse kann die Sichtbarkeit erweitern. Auch bei Ausnahmen kann eine Unterklasse speziellere Ausnahmen bzw. ganz andere Ausnahmen als die Methode der Oberklasse erzeugen.

 

Zum Seitenanfang

7.8.3    Array-Typen und Kovarianz * Zur vorigen ÜberschriftZur nächsten Überschrift

Die Aussage »Wer wenig will, kann viel bekommen« gilt auch für Arrays, denn wenn eine Klasse U eine Unterklasse einer Klasse O ist, ist auch U[] ein Untertyp von O[]. Diese Eigenschaft nennt sich Kovarianz. Da Object die Basisklasse aller Objekte ist, kann ein Object-Array auch alle anderen Objekte aufnehmen.

Bauen wir uns eine statische Methode set(…), die einfach ein Element an die erste Stelle ins Array setzt:

Listing 7.53     src/main/java/com/tutego/insel/oop/ArrayCovariance.java, set()

public static void set( Object[] array, Object element ) {

array[ 0 ] = element;

}

Die Kovarianz ist beim Lesen von Eigenschaften nicht problematisch, beim Schreiben jedoch potenziell gefährlich. Schauen wir, was mit unterschiedlichen Array- und Elementtypen passiert:

Listing 7.54     src/main/java/com/tutego/insel/oop/ArrayCovariance.java, main

Object[] objectArray = new Object[ 1 ];

String[] stringArray = new String[ 1 ];

System.out.println( "It's time for change" instanceof Object ); // true

set( stringArray, "It's time for change" );

set( objectArray, "It's time for change" );

set( stringArray, new StringBuilder("It's time for change") ); // inline image

Der String lässt sich in einem String-Array abspeichern. Der zweite Aufruf funktioniert ebenfalls, denn ein String lässt sich auch in einem Object-Array speichern, da ein Object ja ein Basistyp ist. Vor einem Dilemma stehen wir dann, wenn das Array eine Referenz speichern soll, die nicht typkompatibel ist. Das zeigt der dritte set(…)-Aufruf: Zur Compilezeit ist alles noch in Ordnung, aber zur Laufzeit kommt es zu einer ArrayStoreException:

Exception in thread "main" java.lang.ArrayStoreException: java.lang.StringBuilder

at com.tutego.insel.oop.ArrayCovariance.set(ArrayCovariance.java:5)

at com.tutego.insel.oop.ArrayCovariance.main(ArrayCovariance.java:17)

Das haben wir aber auch verdient, denn ein StringBuilder-Objekt lässt sich nicht in einem String-Array speichern. Selbst ein new Object() hätte zu einem Problem geführt.

Das Typsystem von Java kann diese Spitzfindigkeit nicht zur Übersetzungszeit prüfen. Erst zur Laufzeit ist ein Test mit dem bitteren Ergebnis einer ArrayStoreException möglich. Bei Generics ist dies etwas anders, denn hier sind vergleichbare Konstrukte bei Vererbungsbeziehungen verboten.

 

Zum Seitenanfang

7.8.4    Dynamisch gebunden auch bei Konstruktoraufrufen * Zur vorigen ÜberschriftZur nächsten Überschrift

Dass ein Konstruktor der Unterklasse zuerst den Konstruktor der Oberklasse aufruft, kann die Initialisierung der Variablen in der Unterklasse stören. Schauen wir uns erst Folgendes an:

class Bouncer extends Bodybuilder {

String who = "Ich bin ein Rausschmeißer";

}

Wo wird nun die Variable who initialisiert? Wir wissen, dass die Initialisierungen immer im Konstruktor vorgenommen werden, doch gibt es ja noch gleichzeitig ein super() im Konstruktor. Da die Spezifikation von Java Anweisungen vor super() verbietet, muss die Zuweisung hinter dem Aufruf der Oberklasse folgen. Das Problem ist nun, dass ein Konstruktor der Oberklasse früher aufgerufen wird, als Variablen in der Unterklasse initialisiert wurden. Wenn es die Oberklasse nun schafft, auf die Variablen der Unterklasse zuzugreifen, wird der erst später gesetzte Wert fehlen. Der Zugriff gelingt tatsächlich, doch nur durch einen Trick, da eine Oberklasse (etwa Bodybuilder) nicht auf die Variablen der Unterklasse zugreifen kann. Wir können aber in der Oberklasse genau jene Methode der Unterklasse aufrufen, die die Unterklasse aus der Oberklasse überschreibt. Da Methodenaufrufe dynamisch gebunden werden, kann eine Methode den Wert auslesen:

Listing 7.55     src/main/java/com/tutego/insel/oop/Bouncer.java

class Bodybuilder {



Bodybuilder() {

whoAmI();

}



void whoAmI() {

System.out.println( "Ich weiß es noch nicht :-(" );

}

}



public class Bouncer extends Bodybuilder {



String who = "Ich bin ein Rausschmeißer";



@Override

void whoAmI() {

System.out.println( who );

}



public static void main( String[] args ) {

Bodybuilder bb = new Bodybuilder();

bb.whoAmI();



Bouncer bouncer = new Bouncer();

bouncer.whoAmI();

}

}

Die Ausgabe ist nun folgende:

Ich weiß es noch nicht :-(

Ich weiß es noch nicht :-(

null

Ich bin ein Rausschmeißer

Das Besondere an diesem Programm ist die Tatsache, dass überschriebene Methoden – hier whoAmI() – dynamisch gebunden werden. Diese Bindung gibt es auch dann schon, wenn das Objekt noch nicht vollständig initialisiert wurde. Daher ruft der Konstruktor der Oberklasse Bodybuilder nicht whoAmI() von Bodybuilder auf, sondern whoAmI() von Bouncer. Wenn in diesem Beispiel ein Bouncer-Objekt erzeugt wird, dann ruft Bouncer mit super() den Konstruktor von Bodybuilder auf. Dieser ruft wiederum die Methode whoAmI() in Bouncer auf, und er findet dort keinen String, da dieser erst nach super() gesetzt wird. Schreiben wir den Konstruktor von Bouncer einmal ausdrücklich hin:

public class Bouncer extends Bodybuilder {



String who;



Bouncer() {

super();

who = "Ich bin ein Rausschmeißer";

}

}

Die Konsequenz, die sich daraus ergibt, ist folgende: Dynamisch gebundene Methodenaufrufe über die this-Referenz sind in Konstruktoren potenziell gefährlich und sollten deshalb vermieden werden. Vermeiden lässt sich das, indem der Konstruktor nur private (oder finale) Methoden aufruft, da diese nicht dynamisch gebunden werden. Wenn der Konstruktor eine private (finale) Methode in seiner Klasse aufruft, dann bleibt es auch dabei.

 

Zum Seitenanfang

7.8.5    Keine dynamische Bindung bei überdeckten Objektvariablen * Zur vorigen ÜberschriftZur nächsten Überschrift

Der Kanadier »Furious Pete«[ 166 ](https://guinnessworldrecords.com/news/2016/4/competitive-eater-challenged-to-fastest-time-to-eat-a-12%E2%80%99%E2%80%99-pizza-record-guinnes-426366) isst alles in Rekordzeit; verewigen wir seine Pizzavertilgungsleistungen in einem Java-Programm. Die Oberklasse PizzaEater repräsentiert die Durchschnittsesser, die für eine 12"-Pizza geschätzte 900 Sekunden benötigen. FuriousPete ist eine Spezialisierung und schafft es in 32 Sekunden:

Listing 7.56     src/main/java/com/tutego/insel/oop/FuriousPete.java

class PizzaEater {



int consumptionTime = 900 /* Seconds */;



void eat() {

System.out.printf( "Ich esse in %d Sekunden eine Pizza%n", consumptionTime );

}

}



public class FuriousPete extends PizzaEater {



int consumptionTime = 32 /* Seconds */;



@Override void eat() {

System.out.println( consumptionTime ); // 32

System.out.println( super.consumptionTime ); // 900

System.out.println( this.consumptionTime ); // 32

System.out.println( ((PizzaEater) this).consumptionTime ); // 900

}



public static void main( String[] args ) {

new FuriousPete().eat();

}

}

Die Oberklasse PizzaEater deklariert eine Objektvariable consumptionTime, und auch die Unterklasse deklariert eine Objektvariable mit dem gleichen Namen – es ist in Java zulässig, dass eine Objektvariable eine andere gleich benannte Objektvariable überdeckt.

Die Unterklasse kann mit super.consumptionTime eine Ebene höher kommen. super ist wie this eine spezielle Referenz und kann auch genauso eingesetzt werden, nur dass super in den Namensraum der Oberklasse geht. Eine Aneinanderreihung von super-Schlüsselwörtern bei einer tieferen Vererbungshierarchie ist nicht möglich. Hinter einem super muss direkt eine Objekteigenschaft stehen, und Anweisungen wie super.super.consumptionTime sind somit immer ungültig.

Bei Methodenaufrufen bindet das Laufzeitsystem immer dynamisch; bei Zugriffen auf Objektvariablen ist das nicht so: Hier bestimmt der Compiler, von welcher Klasse die Objektvariable genommen werden soll. Unser Programm zeigt das an der Anweisung:

System.out.println( ((PizzaEater) this).consumptionTime );     // 900

Die Ausgabe 900 ist identisch mit System.out.println(super.consumptionTime). Die this-Referenz hat in dem Kontext den Typ FuriousPete. Wenn wir den Typ aber in den Basistyp PizzaEater konvertieren, bekommen wir genau die Belegung von consumptionTime aus der Basisklasse unserer Hierarchie. Eine explizite Typumwandlung in Richtung eines Obertyps ist bei dynamisch gebundenen Methodenaufrufen auch nie nötig; die Laufzeitumgebung entscheidet selbstständig, wohin der Aufruf geht. Setzen wir in die eat()-Methoden vom FuriousPete die Zeile

((PizzaEater) this).eat();

ist das identisch mit

eat();

also eine Rekursion.

 


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