Eine Klasse kann Felder und Methoden besitzen. Eine Klasse kann wiederum von
einer anderen Klasse abgeleitet werden. Dieser Vorgang wird
als Vererbung (engl. inheritance) bezeichnet. Eine Klasse kann
daher Felder und Methoden von einer bereits definierten Klasse erben. Die Vererbung
schafft im Rahmen der objektorientierten Programmierung (OOP) die Möglichkeit, auf
bestehende Klassen aufzubauen (Wiederverwendbarkeit von Programmcode). In diesem
Zusammenhang wird von einer Klassenhierarchie gesprochen. Klassen, die abgeleitet
werden, werden als Basisklassen, Oberklassen oder Superklassen
bezeichnet. Eine abgeleitete (erbende) Klasse wird als Unterklasse oder
Subklasse bezeichnet. Die Java-Programmiersprache bietet die
Möglichkeit einer "einfachen Klassenvererbung". Eine Klasse kann also nur eine
einzige Klasse als direkte Superklasse besitzen. Die
Mehrfach-Klassenvererbung hingegen wird nicht unterstützt (aus einer Mehrfachvererbung
können Probleme resultieren, die an dieser Stelle nicht näher erläutert werden
sollen).
Eine besondere Rolle bei der Vererbung spielt die Klasse Object
.
Sie ist im Paket java.lang
enthalten und ist die Wurzel einer jeden
Klassenhierarchie und damit auch Superklasse einer jeden Klasse (Object
muss keine direkte Superklasse sein). Die Wurzelklasse stellt u.a. die folgenden
Methoden zur Verfügung.
API
java.lang
Class Object
Methode:
boolean equals(Object obj)
int hashCode()
Class<?> getClass()
String toString()
Die Methode equals
gibt den Wert true
zurück,
falls das als Argument übergebene Objekt mit dem Objekt übereinstimmt, von dem
aus diese Methode aufgerufen wird.
Mit Hilfe der Methode hashCode
kann ein Objekt auf eine
Ganzzahl im int
-Zahlenbreich abgebildet werden. Diese Hashcodewerte
werden auch im Rahmen von Collection-Klassen verwendet, die eine Hierarchie von fertig
implementierten Datenstrukturen bilden und mit dem JDK zur Verfügung gestellt
werden (z.B. HashMap
, Hashtable
). Eine Datenstruktur ist
ein Objekt zur Speicherung von Daten. "Hash-Klassen" befinden sich im Paket
java.util
und werden auch als hash-basierte Container bezeichnet.
Hashcodewerte werden dazu verwendet, um Objekte in einem solchen Container
abzulegen bzw. zu finden. Zwischen den Methoden equals
und
hashCode
bestehen gewisse Abhängigkeiten. So müssen z.B. zwei
gleiche Objekte im Sinne der Methode equals
auch identische
Hashcodewerte besitzen.
Der Rückgabewert von getClass
liefert die Laufzeitklasse für das
Objekt, von dem aus diese Methode aufgerufen wurde. Der Ergebnistyp dieser
Methode ist mit Class<?>
angegeben. Innerhalb der spitzen
Klammern steht ein Fragezeichen und wird als Wildcard bezeichnet. Spitze
Klammerpaare und deren Inhalte sind Teil der Syntax zu Java-Generics (siehe
dazu Abschnitt 3).
Der Aufruf von toString
gibt eine Zeichenkette zurück, die als eine
String-Repräsentation eines Objekts angesehen wird. Das Objekt wird sozusagen
textuell repräsentiert, um "lesbare" Informationen über das Objekt auf den
Bildschirm ausgeben zu können.
Die geannten Methoden der Klasse Object
sollen nun konkret im
folgenden Beispiel aufgerufen werden. Es wird darin die Klasse
ObjectExample
definiert. Von dieser werden innerhalb der
main
-Methode einige Objekte angelegt. Diese Objekte werden dazu
verwendet, um mit Hilfe der Punktnotation die Methoden equlas
,
getClass
, hashCode
und toString
aufzurufen.
Der Klassenkörper enthält keine Definition dieser Methoden, sie können aber
dennoch aufgerufen werden, da sie von der Wurzelklasse geerbt wurden. Dies
gilt automatisch und bedarf keiner expliziten Angaben. Der Java-Compiler erkennt
diese Methoden als die in der Wurzelklasse deklarierten.
Listing 1.25. ObjectExample.java
. Verwendung der automatisch von
Object
geerbten Methoden equals
, getClass
,
hashCode
und toString
.
public class ObjectExample {
public static void main(String args[]) {
ObjectExample oe = new ObjectExample();
ObjectExample oe2 = new ObjectExample();
ObjectExample oe3 = oe;
boolean b = oe.equals(oe2);
boolean b2 = oe.equals(oe3);
System.out.println(b + ", " + b2);
Class<?> clazz = oe.getClass();
System.out.println(clazz);
int i = oe.hashCode();
String s = oe.toString();
System.out.println(s + ", " + i);
}
}
Das kurze Listing erzeugt drei Zeilen als Ausgabe:
false, true
class ObjectExample
ObjectExample@14b7453, 21722195
Die erste Zeile verdeutlicht, dass die Objekte oe
und
oe2
nicht gleich im Sinne von equals
sind und der
Rückgabewert true
wird zurückgegeben, da oe
und
oe3
als gleich betrachtet werden. Vom Objekt oe
aus
wird getClass
aufgerufen. Auf dem Bildschirm wird durch diesen
Aufruf "class ObjectExample" ausgegeben, da das Objekt oe
von der
Klasse ObjectExample
erzeugt wurde. clazz
wird direkt
als Argument in println
verwendet, wobei die Methode das Argument
vor der Bildschirmausgabe automatisch in eine Zeichenkette konvertiert.
Die letzte Zeile auf dem Bildschirm besteht aus zwei Teilen. Der erste Teil
resultiert aus dem Aufruf von toString
und wird durch das at-Zeichen
"@" noch einmal unterteilt. Vor dem Trennzeichen steht wiederum die Klasse, von
der das Objekt erzeugt wurde und dahinter folgt die hexadezimale Zahlenangabe des
Hashcodes des Objekts. Die Zahl 21722195 ist die Ganzzahl, die durch expliziten
Aufruf der dafür vorgesehenen Methode hashCode
erhalten wurde. Es
gilt: 21722195 = 0x14b7453 (das Präfix "0x" kennzeichnet die hexadezimale
Zahlenangabe als solche).
1.7.2. Erweitern (ableiten) einer Klasse
Um eine bestehende Klasse abzuleiten bzw. zu erweitern ist die
extends
-Klausel vorgesehen. Innerhalb der Deklaration der neu zu
erstellenden Klasse kann das Schlüsselwort extends
angegeben
werden. Danach besteht die Möglichkeit diejenige Klasse zu notieren, die abgeleitet
werden soll.
KlassenModifiers_opt class KlassenName2 extends KlassenName1
{
...
}
Als Beispiel zur Ableitung von Klassen sollen die zwei Klassen
InheritanceExample
und InheritanceExample2
erstellt
werden. Die erste genannte Klasse soll einer einfache Erweiterung von
Object
sein und die zweite soll wiederum InheritanceExample
ableiten. Klassen können in eine grafische Notation überführt werden, die als
Klassendiagramm bezeichnet wird. Die folgende Abbildung
zeigt ein einfache Vererbungshierarchie, die aus den drei genannten Klassen
besteht.
Die Abbildung zeigt die einfachste Form der grafischen Darstellung einer
Klasse. Eine Klasse wird darin durch ein Rechteck repräsentiert, in dem der Name
der Klasse notiert ist. Die Klassen sind durch Linien miteinander verbunden.
Ein Ende der Linien ist mit einer nicht ausgefüllte Pfeilspitze versehen, die
auf die jeweilige direkte Superklasse der entsprechenden Klasse zeigt.
Im Sinne der Pfeilrichtung wird auch von einer Generalisierung und entgegen der
Pfeilrichtung von einer Spezialisierung von Klassen gesprochen. Die Klasse
InheritanceExample2
spezialisiert also InheritanceExample
.
Durch den anschließenden Quelltext wird die Klasse InheritanceExample
definiert. Sie soll eine Ableitung von Object
sein.
(die Ausgangsklasse Object
gehört dem Paket java.lang
an und wird dadurch automatisch importiert).
Listing 1.26. InheritanceExample.java
. Erweiterung der Wurzelklasse
Object
.
public class InheritanceExample extends Object {
int a;
public void method1() {
System.out.print("Hello");
}
}
Das Listing enthält eine extends
-Klausel, die als direkte
Superklasse die Klasse Object
angibt. Die Angabe der Klausel ist
im Beispiel nicht notwendig, da bei einer nicht vorhandenen
extends
-Klausel automatisch Object
als direkte Superklasse
der zu erstellenden Klasse gesetzt wird. Die Verwendung der Klausel im
Beispiel wird aber auch nicht als Fehler gewertet. Das Beispiel deklariert die
Instanzvariable a
und die Instanzmethode method1
.
Die gerade definierte Klasse InheritanceExample
soll nun wiederum
abgeleitet werden. Die direkte Subklasse soll InheritanceExample2
sein.
Listing 1.27. InheritanceExample2.java
. Vererbung der Klasse
InheritedExample
.
public class InheritanceExample2 extends InheritanceExample {
int b;
public void method2() {
System.out.println(" World!");
}
}
InheritanceExample2
deklariert die Instanzvariable b
und die Instanzmethode method2
. Sie erbt auch a
und
method1
aus Listing 1.26. Dies kann
verdeutlicht werden, indem im anschließenden Testprogramm nur ein Objekt von
InheritanceExample2
erzeugt wird und von diesem aus mit Hilfe der
Punktnotation auf a
, b
, method1
und
method2
zugegriffen wird.
/* Test.java */
public class Test {
public static void main(String args[]) {
InheritanceExample2 ie2 = new InheritanceExample2();
ie2.a = 3;
ie2.b = 4;
ie2.method1();
ie2.method2();
System.out.println(ie2.a + ", " + ie2.b);
}
}
Das kurze Testprogramm gibt zwei Zeilen aus:
Hello World!
3, 4
In ein Klassendiagramm können neben dem Namen der Klasse auch weitere
Details der Klasse aufgenommen werden. Dazu kann das Rechteck, das für eine Klasse
steht, in insgesamt drei Bereiche unterteilt werden. Im obersten Bereich steht
weiterhin der Klassenname. Im mittleren Bereich können die Felder und im untersten
Bereich die Methoden der Klasse notiert werden. Die nächste Abbildung zeigt
ein detailliertere grafische Notation der Klassen InheritanceExample
und InheritanceExample2
.
Die Einträge im mittleren Bereich des Klassendiagramms werden auch als
Attribute der Klasse bezeichnet. Der unterste Bereich
enthält die Methoden der Klassen (Anschnitt der Operationen).
Vor den Einträgen zu den Instanzvariablen a
und b
steht
an erster Stelle das Schlüsselwort package
. Innerhalb der beiden
Java-Quelltexte aus Listing 1.26 und
Listing 1.27 sind die beiden Variablen ohne
Modifier deklariert. Der Zugriff auf die beiden Felder kann daher nur von Klassen
aus erfolgen, die sich im gleichen Paket befinden (Paket-Ebene, Klassen
können mit Hilfe des Schlüsselwortes package
im Java-Quelltext zu
Klassenbibliotheken zusammengefasst werden). Die explizite Aufnahme des Wortes
package
in das Klassendiagramm signalisiert diese eingeschränkte
Sichtbarkeit.
Keine Vererbung von Konstruktoren
Konstruktoren werden bei der Ableitung einer Klasse nicht vererbt, da sie
nicht als Members (Mitglieder) einer Klasse angesehen werden.
Members einer Klasse sind Feld- und Methoden, die in der Klasse deklariert wurden.
Auch Felder und Methoden, die aus einer direkten Superklasse geerbt wurden werden
als Members der erbenden Klasse betrachtet.
Welche Members eine Klasse von einer anderen erben kann liegt auch an den
Modfiern, mit denen Members versehen wurden (siehe Abschnitt 1.7.4).
Ein Konstruktor muss daher in einer abgeleiteten Klasse gegebenenfalls entprechend
deklariert werden.
1.7.3. Explizite Konstruktoraufrufe
Innerhalb eines Konstruktors kann als erste Anweisung ein Aufruf eines
weiteren Konstruktors der selben Klasse oder einer direkten Superklasse stehen.
Ein solcher Aufruf wird innerhalb der Spezifikation der Programmiersprache Java
als expliziter Konstruktoraufruf bezeichnet.
Explizite Konstruktoraufrufe können mit this
und super
erfolgen. Den Schlüsselwörtern schließt sich in diesem Fall ein rundes
Klammerpaar an, in dem entsprechende Argumente stehen können.
this(ArgumentListe_opt)
super(ArgumentListe_opt)
Das anschließende Beispiel erweitert die Klasse ConstructorExample
aus Listing 1.15. Innerhalb der
main
-Methode wird ein neues Objekt erzeugt, indem zunächst der
Konstruktor mit lediglich einem einzigen Parameter aufgerufen wird.
Dieser Konstruktor enthält als erste Anweisung this(5, 6);
. Diese
Anweisung führt zum Aufruf des zweiten Konstruktors mit zwei Parametern. Dieser
wiederum ruft mit super(3, 4);
den einzigen Konstruktor der
direkten Superklasse ConstructorExample
auf und initialisiert
die darin deklarierten Instanzvariablen a
und b
.
Nach diesem Aufruf werden auch den Variablen c
, d
und e
entsprechende Werte zugeordnet.
Listing 1.28. InheritanceExample2.java
. (Explizite) Konstruktoraufrufe mit
Hilfe der Schlüsselwörter this
und super
aus einem
Konstruktor heraus.
public class InheritanceExample2 extends ConstructorExample {
int c;
int d;
int e;
public InheritanceExample2(int c, int d) {
super(3, 4);
this.c = c;
this.d = d;
}
public InheritanceExample2(int e) {
this(5, 6);
this.e = e;
}
public static void main(String[] args) {
InheritanceExample2 ie2 = new InheritanceExample2(7);
System.out.println(ie2.a + ", " + ie2.b + ", " + ie2.c + ", " +
ie2.d + ", " + ie2.e);
}
}
Das Listing gibt die Zahlen 3, 4, 5, 6 und 7 auf dem Bildschirm aus.
Die in ConstructorExample
angegebenen Variablen a
und
b
sind aufgrund Vererbung zugänglich.
1.7.4. Modifier und Vererbung
Das Listing 1.26 beinhaltet die Instanzvariable
a
und die Methode method1
. Bei der Variablen wurde auf
einen Modifier vezichtet und die Methode besitzt den Modifier public
.
Das Weglassen oder die Verwendung unterschiedlicher Modifier bei der Deklaration
von Feldern und Methoden hat Einfluss auf die Vererbung. Wird z.B.
method1
im Beispiellisting nicht mit public
sondern
mit private
deklariert, erfolgt keine Vererbung der Methode in eine
Subklasse. Es ist auch entscheidend, ob sich die vererbende Klasse und die
erbende Klasse im selben Paket befinden. Wie sich unterschiedliche Modifier, auch
im Hinblick auf eine Paketbetrachtung, auf die Vererbung auswirken soll im
Folgenden näher betrachtet werden. Dazu werden drei Dateien benötigt. Eine
Quelldatei Test.java
soll sich dabei im
Ausgangsverzeichnis befinden. ScopeExample.java
aus
Listing 1.22 und die noch zu erstellende Datei
TestP1.java
sind im Unterverzeichnis p1
lokalisiert
(der Ordner p1
enthält die Dateien, die zum Paket p1
gehören sollen).
Ausgangsverzeichnis: Test.java
Ausgangsverzeichnis/p1: ScopeExample.java
TestP1.java
Vererbung mit Standardzugang
Felder und Methoden, für die kein Modifier verwendet wurde, haben
definitionsgemäß Standardzugang (default access). Sie werden nicht in Klassen
vererbt, die außerhalb des Pakets diejenige Klasse erweitern, die diese Felder und
Methoden deklariert. Die Klasse Test
soll ScopeExample
erweitern. Innerhalb von Test.java
wird das Schlüsselwort
package
nicht verwendet und daher befindet sich die Klasse
Test
definitionsgemäß im Default-Paket (unnamed package). Die Angabe
"package p1
" innerhalb von ScopeExample.java
ordnet
die Klasse ScopeExample
dem Paket p1
zu (die Klassen
Test
und ScopeExample
befinden sich also in
unterschiedlichen Paketen). Das anschließende Listing gibt entsprechende
Fehlermeldungen aus, wenn im Ausgangsverzeichnis "javac Test.java
"
aufgerufen wird (ScopeExample.class
befindet sich im Unterverzeichnis
p1
).
/* Test.java */
public class Test extends p1.ScopeExample {
public Test() {
a = 3;
// Fehler (Compiler): a is not public in p1.ScopeExample;
// cannot be accessed from outside package
b = 4;
c = 5;
d = 6;
// Fehler (Compiler): d has private access in p1.ScopeExample
method1();
// Fehler (Compiler): cannot find method method1()
method2();
method3();
method4();
// Fehler (Compiler): cannot find method method4()
}
public static void main(String[] args) {
Test t = new Test();
}
}
Auf die Instanzvariablen a
und b
kann nicht
zugegriffen werden und auch der Aufruf der Methoden method1
und
method4
wird durch den Compiler moniert. Die genannten Mitglieder der
Klasse ScopeExample
werden nicht vererbt, da sie entweder
Standardzugang besitzen (wirkt nicht paketübergreifend) oder mit private
deklariert wurden. Felder und Methoden mit Standardzugang werden hingegen in eine
Subklasse vererbt, die sich im gleichen Paket wie die vererbende Klasse befindet.
Vererbung mit public
und
protected
In der Klasse ScopeExample
sind die Instanzvariablen
b
und c
mit den Schlüsselwörtern public
bzw.
protected
versehen. Diese beiden Modifier wurden ebenfalls bei
der Deklaration von method2
und method3
in dieser
Beispielklasse verwendet. Felder und Methoden, die mit public
bzw.
protected
deklariert wurden, werden in Subklassen vererbt. Dies ist
auch der Fall, wenn die Subklasse Bestandteil eines anderen Pakets ist.
Vererbung mit private
Felder und Methoden, die mit dem Modifier private
deklariert
wurden, werden nicht vererbt (auch wenn sie sich im gleichen Paket befinden).
Der Aufruf des Java-Compilers
Ausgangsverzeichnis> javac p1/TestP1.java
erzeugt zwei Fehlermeldungen (TestP1.class
und
ScopeExample.class
befinden sich im Unterverzeichnis p1
).
/* TestP1.java */
package p1;
public class TestP1 extends ScopeExample {
public TestP1() {
a = 3;
b = 4;
c = 5;
d = 6;
// Fehler (Compiler): d has private access in p1.ScopeExample
method1();
method2();
method3();
method4();
// Fehler (Compiler): cannot find method method4()
}
public static void main(String[] args) {
TestP1 t = new TestP1();
}
}
Zusammenfassung
Für die Vererbung von Feldern und Methoden können die folgenden Aussagen
getroffen werden:
-
Standardzugang (default access):
Die Vererbung von Feldern und Methoden mit Standardzugang (Deklaration ohne
Modifier) wirkt nicht paketübergreifend. Eine Vererbung innerhalb des gleichen
Pakets ist aber möglich.
-
public
und protected
:
Felder und Methoden, die mit public
bzw. protected
deklariert wurden, werden in Subklassen vererbt. Dies ist auch der Fall, wenn die
Subklasse Bestandteil eines anderen Pakets ist.
-
private
:
Felder und Methoden, die mit dem Modifier private
deklariert wurden,
werden nicht vererbt (die Paketzugehörigkeit von erbender und vererbender Klasse
spielt dabei keine Rolle).
1.7.5. Overriding und Hiding
Wenn eine Klasse abgeleitet wird, werden auch Instanzmethoden in eine
Subklasse vererbt (bei entsprechender Wahl des Methodenmodifier). Die Subklasse
enthält nun die geerbten Instanzmethoden und kann zusätzlich neue Methoden
deklarieren. Es ist erlaubt, dass Name, Parameter (Typ und Anzahl) und Rückgabewert
der neu zu erstellenden Instanzmethode genauso gewählt werden wie bei der von der
Superklasse geerbten Methode. Die geerbte Instanzmethode der Superklasse wird
in diesem Fall innerhalb der Subklasse überschrieben (überlagert). Eine
Überlagerung einer Instanzmethode innerhalb einer Subklasse
findet also statt, wenn die Signatur und der Rückgabewert der in dieser Klasse
neu erstellten Instanzmethode mit der Singnatur und dem Rückgabewert einer
Instanzmethode der zugehörigen Superklasse übereinstimmt. Mit der Überschreibung
(engl. override) von Instanzmethoden innerhalb einer Subklasse können geerbte
Methoden angepasst werden. Wird von dieser Subklasse nun ein Objekt erzeugt,
kann mit Hilfe der Punktnotation diese überlagerte Instanzmethode aufgerufen
werden. Ohne eine Überlagerung würde in diesem Fall der automatische Aufruf der
entsprechenden Methode der Superklasse erfolgen.
Bisher wurde nur von Instanzmethoden ausgegangen. Innerhalb einer
Subklasse können auch Klassenmethoden (statische Methoden) deaklariert werden,
welche Klassenmethoden einer Superklasse verdecken (engl. hide).
Im Gegensatz zu Instanzmethoden wird bei Klassenmethoden von
Verdeckung gesprochen (Klassenmethoden existiert unabhängig
von Objekten und können ohne eine Referenz bezüglich eines bestimmten Objekts
aufgerufen werden). Eine Klassenmethode der Superklasse wird in einer Subklasse
verdeckt, wenn die Signaturen und die Rückgabewerte der betrachteten Methoden
übereinstimmen.
Auch Felder innerhalb einer Subklasse können Felder ihrer Superklasse verdecken.
Das Überlagern von Instanzmethoden und das Verdecken von Klassenmethoden und
Feldern zeigt das anschließende Beispiel.
Listing 1.29. OverrideHideExample.java
. Deklariert Methoden und Felder,
die in einer abgeleiteten Klasse überlagert bzw. verdeckt werden sollen.
public class OverrideHideExample {
static int a = 3;
int b = 4;
String c = "Hello World!";
public static void method1() {
System.out.println("method1");
}
public static void method2() {
System.out.println("method2");
}
public void method3() {
System.out.println("method3");
}
public void method4() {
System.out.println("method4");
}
}
OverrideHideExample.java
deklariert die statische Variable
(Klassenvariable) a
und die beiden Instanzvariablen b
und c
. Das Listing beinhaltet auch jeweils zwei Klassen- und
Instanzmethoden. Der anschließende Java-Quelltext soll nun die zuvor definierte
Klasse OverrideHideExample
erweitern, wobei vererbte Methoden
und Felder überlagert bzw. verdeckt werden sollen.
Listing 1.30. OverrideHideExample2.java
. Überlagert Methoden bzw. verdeckt
Methoden und Felder der direkten Superklasse OverrideHideExample
.
public class OverrideHideExample2 extends OverrideHideExample {
float a = 5.5f;
boolean b = true;
public static void method1() {
System.out.println("method21");
}
public void method3() {
System.out.println("method23");
}
public void method4(String s) {
System.out.println("Hello " + s);
}
public static void main(String[] args) {
OverrideHideExample2 ohe2 = new OverrideHideExample2();
ohe2.method3();
ohe2.method4();
ohe2.method4("Java!");
System.out.println("-----");
OverrideHideExample.method1();
OverrideHideExample2.method1();
OverrideHideExample2.method2();
System.out.println("-----");
System.out.println(ohe2.a + ", " + ohe2.b + ", " + ohe2.c);
System.out.println(OverrideHideExample.a);
}
}
Das Listing enthält eine main
-Methode und kann direkt
ausgeführt werden, was zur folgenden Ausgabe führt:
(a) method23
(b) method4
(c) Hello Java!
-----
(d) method1
(e) method21
(f) method2
-----
(g) 5.5, true, Hello World!
(h) 3
Zu den einzelnen Zeilen der Bildschirmausgabe können die folgenden Aussagen
gemacht werden:
-
Ausgabezeile a. Es wird ein Objekt
ohe2
der Klasse OverrideHideExample2
erzeugt. Von
diesem aus wird mit Hilfe der Punktnotation die Methode method3
aufgerufen. Die aufgerufene Methode ist die überlagerte Version der entsprechenden
Methode aus OverrideHideExample
.
-
Ausgabezeile b. Mit Hilfe des Objekts
ohe2
wird die Instanzmethode method4
aufgerufen.
Diese Methode wurde von OverrideHideExample2
geerbt und darin nicht
überlagert.
-
Ausgabezeile c. Die Ausgabe resultiert
aus dem Aufruf der Instanzmethode method4
der Klasse
OverrideHideExample2
, wobei die genannte Methode einen Parameter
besitzt und deren Signatur damit nicht mit der Signatur der gleichnamigen Methode
der Klasse OverrideHideExample
übereinstimmt. Eine Überlagerung
findet in diesem Fall nicht statt.
-
Ausgabezeile d. Mit
OverrideHideExample.method1();
wird die entsprechende statische
Methode der Klasse OverrideHideExample
aufgerufen.
-
Ausgabezeile e. Mit
OverrideHideExample2.method1(); wird die statische Methode method1
aufgerufen, die innerhalb der Klasse OverrideHideExample2
deklariert wurde. Die aufgerufene Methode verdeckt die gleichnamige Methode aus
OverrideHideExample
.
-
Ausgabezeile f.
OverrideHideExample2
enthält keine Deklaration einer Klassenmethode
mit dem Namen method2
. Der Aufruf von
OverrideHideExample2.method2();
führt daher zur Ausführung der
entsprechenden Methode aus OverrideHideExample
.
-
Ausgabezeile g. Unter Verwendung eines
Objekts ohe2
der Klasse OverrideHideExample2
wird
auf Felder mit den Namen a
, b
und c
zugegriffen. Wie die Ausgabe von "5.5, true, Hello World!" zeigt, verdecken
die Felder a
und b
die gleichnamigen Felder der
Superklasse OverrideHideExample
. Das Feld c
wurde
vererbt. a
und b
besitzen unterschiedliche Typen
in Super- bzw. Subklasse, was zulässig ist. Das Feld a
der
Superklasse wurde mit static
deklariert und die Deklaration von
a
innerhalb von OverrideHideExample2
deklariert eine
Instanzvariable - auch dieses ist erlaubt.
-
Ausgabezeile h. Mit
OverrideHideExample.a
wird direkt die Klassenvariable a
der Klasse OverrideHideExample
angesprochen, wie durch die Ausgabe
von 3 deutlich wird.
Die verwendeten Modifier von überschreibender und überschriebener Methode
können sich unterscheiden. Es ist z.B. zulässig, wenn eine
protected
-Instanzmethode einer Superklasse durch eine
public
-Instanzmethode einer Subklasse überschrieben wird; eine
private
-Instanzmethode als überschreibende Methode ist aber nicht
zulässig. Es wird also erlaubt, dass der überschreibenden Methode erweiterte
Zugangsprivilegien eingeräumt werden. Bei der Verwendung des Schlüsselwortes
static
muss darauf geachtet werden, dass überschreibende und
überschriebene Methode entweder beide mit static
deklariert werden
oder beide nicht.
1.7.6. Finale Klassen und finale Methoden
Die Möglichkeit der Vererbung von Klassen kann unterbunden werden. Dazu
kann eine Klasse mit dem Modifier final
deklariert werden. Finale
Klassen können danach nicht mehr abgeleitet werden und die Vererbungslinie
bezüglich dieser Klasse wäre somit unterbrochen. Da finale Klassen keine
Subklassen haben können, können z.B. auch keine Instanzmethoden von finalen
Klassen überlagert werden. Um ein Überlagern von Methoden zu verhindern, könnten
aber auch Methoden von nicht finalen Klassen mit dem Schlüsselwort final
deklariert werden. Es besteht auch die Möglichkeit, Felder einer Klasse mit
final
zu deklarieren. Der Wert, der einer finalen Instanz- bzw.
Klassenvariablen bei der Deklaration zugewiesen wird, kann anschließend nicht mehr
verändert werden. Das folgende Beispiellisting verwendet den Modifier
final
, um Felder und Methoden damit zu deklarieren.
Listing 1.31. FinalExample.java
. Deklaration von finalen Feldern und
finalen Methoden.
public class FinalExample {
final static int a = 3;
final int b = 4;
public final static void method1() {
System.out.println("Hello");
}
public final void method2() {
System.out.println("World!");
}
}
Die genannten Eigenschaften von finalen Feldern und finalen Methoden können
durch den kurzen Java-Quelltext Test.java
überprüft werden. Ein
Verdecken der statischen Methode method1
bzw. ein Überlagern von
method2
führen erwartungsgemäß zu Fehlermeldungen des Compilers.
Auch nachträgliche Wertzuweisungen für die Felder a
und b
innerhalb der Subklasse von FinalExample
führen zum Abbruch der
Übersetzung.
/* Test.java */
public class Test extends FinalExample {
public static void method1() {
System.out.println("Hans");
}
// Fehler (Compiler): method1() in Test cannot override method1()
// in FinalExample; overridden method is static final
public void method2() {
System.out.println("im Glueck");
}
// Fehler (Compiler): method2() in Test cannot override method2()
// in FinalExample; overridden method is final
public static void main(String[] args) {
Test.a = 5;
// Fehler (Compiler): cannot assign a value to final variable a
Test t = new Test();
t.b = 7;
// Fehler (Compiler): cannot assign a value to final variable b
}
}
Wird z.B. die Klasse Test
mit dem Modifier final
deklariert, wird bei dem Versuch der Ableitung dieser finalen Klasse umgehend
eine entsprechende Fehlermeldung durch den Java-Compiler generiert.
/* Test.java */
public final class Test extends FinalExample {
...
}
/* Test2.java */
public class Test2 extends Test {
...
}
// Fehler (Compiler): cannot inherit from final Test