javaseiten.de   |   Version 0.6
 

1.7. Vererbung

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

1.7.1. Die Klasse Object

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.

/* ObjectExample.java */

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.

Abbildung 1.1. Klassendiagramm: Einfache Vererbungshierarchie mit der Wurzelklasse Object.

classdiagram-obj-ie-ie2.jpg

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.

/* InheritanceExample.java */

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.

/* InheritanceExample2.java */

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.

Abbildung 1.2. Klassendiagramm: Die Klasse InheritanceExample2 spezialisiert InheritanceExample.

classdiagram-ie-ie2.jpg

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.

/* InheritanceExample2.java */

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.

/* OverrideHideExample.java */

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.

/* OverrideHideExample2.java */

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.

/* FinalExample.java */

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

 

 

 

Diese Seite nutzt Google-Dienste - siehe dazu Datenschutz.

Copyright © 2006, 2007 Harald Roeder