Überlagern von Methoden und dynamisches Binden in Java

In Java werden Methoden der Vaterklasse an die abgeleiteten Klassen vererbt. Die vererbten Methoden können dann in den abgeleiteten Klassen neu definiert werden. Dieser Vorgang wird in Java als das Überlagern von Methoden bezeichnet.

Zusätzlich können in den Tochterklassen neue Methoden definiert oder die geerbten Methoden unverändert übernommen werden. Somit können in einer abgeleiteten Klasse drei Arten von Methoden vorhanden sein:

  • In der Tochterklasse definierte neue Methoden
  • Von der Vaterklasse geerbte unveränderte Methoden
  • Von der Vaterklasse geerbte überlagerte Methoden, die in der Tochterklasse neu definiert wurden
plhq_teaser_hbox_gelb_fotolia_RA Studio_46292813

Unser großes
Android Online-Kurs
Gesamtpaket



Weitere Infos

Wurde eine Methode überlagert (d.h. in der abgeleiteten Klasse neu definiert), wird beim Aufrufen der Methode auf Objekten dieses Datentyps immer die überlagerte Version verwendet.

Betrachten wir nun ein Quellcode-Beispiel für eine einfache Methodenüberlagerung:

Beispielanwendung für die Methodenüberlagerung in Java

/*
* Beispielanwendung für das Überlagern von Methoden in Java
*/

public class MethodenÜberlagern
{
  public static void main (String[] args)
  {
     Lebewesen tier1 = new Lebewesen();
     System.out.println("\n- Tier 1 -> Lebewesen tier1 = new Lebewesen();\n");
     System.out.println("Datentyp der Referenzvariable = Lebewesen");
     System.out.println("Datentyp der Instanz          = Lebewesen");
     System.out.println("Tierart                       = " + tier1.getArt());

     Lebewesen tier2 = new Saeugetier();
     System.out.println("\n\n- Tier 2 -> Lebewesen tier2 = new Saeugetier();\n");
     System.out.println("Datentyp der Referenzvariable = Lebewesen");
     System.out.println("Datentyp der Instanz          = Saeugetier");
     System.out.println("Tierart                       = " + tier2.getArt());

     Saeugetier tier3 = new Saeugetier();
     tier3.tragzeit = 33;  
     System.out.println("\n\n- Tier 3 -> Saeugetier tier3 = new Saeugetier();\n");
     System.out.println("Datentyp der Referenzvariable = Saeugetier");
     System.out.println("Datentyp der Instanz          = Saeugetier");
     System.out.println("Tierart                       = " + tier3.getArt());
     System.out.println("Tragzeit                      = " + tier3.getTragzeit() + " Wochen");
  }
}

class Lebewesen
{
  String getArt()
  {
    return "Lebewesen";
  }
}

class Saeugetier extends Lebewesen
{
  short tragzeit;
 
  short getTragzeit()
  {
    return this.tragzeit;
  } 

  String getArt()
  {
    return "Saeugetier";
  }
}

In dem oberen Code-Beispiel wird die Klasse Saeugetier von der Basisklasse Lebewesen abgeleitet. Dadurch erbt die Saeugetier-Klasse die Merkmale der Basisklasse. Wir testen die Anwendung mittels der Klasse MethodenÜberlagern. Es ist zu beachten, dass die Klasse MethodenÜberlagern die inneren Klassen Saeugetier und Lebewesen enthält.

In Zeile 9 wird zunächst ein Objekt vom Typ Lebewesen instanziert und anschließend der Objektvariable tier1 vom Typ Lebewesen zugewiesen.

In Zeile 15 wird ein Objekt vom Typ Saeugetier instanziert und der Variable tier2 vom Typ Lebewesen zugewiesen.

In Zeile 21 wird ein Objekt vom Typ Saeugetier instanziert und der Variable tier3 vom Typ Saeugetier zugewiesen.

In Java können nur die Methoden des Datentyps der Referenzvariablen aufgerufen werden. Diese Methoden können in abgeleiteten Klassen überlagert, sprich neu definiert, sein. Welche Version der Methoden (Ur-Version der Basisklasse oder neudefinierte Version der abgeleiteten Klasse) zur Laufzeit aufgerufen werden, legt der Datentyp der Instanz fest, über welche die Methode aufgerufen wird.

In Zeile 9 ist die Objektvariable (Referenzvariable) vom Datentyp Lebewesen. Daher kann nur die Methode getArt() auf Objekten, die in dieser Variable gespeichert sind, aufgerufen werden.

In Zeile 21 ist die Objektvariable vom Datentyp Saeugetier. Daher können die Methoden getArt() und getTragzeit() auf Objekten, die in dieser Variable gespeichert sind, aufgerufen werden.

In Zeile 15 ist die Objektvariable vom Datentyp Lebewesen, aber in ihr wird ein Objekt vom Typ Saeugetier gespeichert. Somit kann zwar nur die Methode getArt() aufgerufen werden, aber aufgrund des Datentyps des Objekts (Saeugetier) wird zur Laufzeit die überlagerte Version der Methode getArt() aufgerufen. Diese Methode wird in der abgeleiteten Klasse Saeugetier neu definiert und überlagert dadurch die Version der Basisklasse Lebewesen.

Starten wir nun die Beispielanwendung, so wird uns die unten abgebildete Kommandozeilenausgabe ausgegeben.

Java Methoden überlagern

Methoden überlagern in Java – Ausgabe der Beispielanwendung

Dynamisches Binden durch Methodenüberlagerung

In dem oberen Programmbeispiel wird die Methode getArt() in der Basisklasse Lebewesen erstmals definiert. Diese Methode wird dann in der abgeleiteten Klasse Saeugetier neu definiert. Somit wird die Methode getArt() zweimal definiert, einmal in der Basisklasse und einmal in der abgeleiteten Klasse.

Welche Methodenversion wird nun zur Laufzeit aufgerufen?

In Java werden Methoden immer über Objekte aufgerufen. Ist das Objekt vom Datentyp Saeugetier, dann wird die Methodenversion von getArt() aufgerufen, die in der Klasse Saeugetier definiert ist. Ist das Objekt vom Typ Lebewesen, wird die Version von getArt() aufgerufen, die in der Klasse Lebewesen definiert ist.

Somit wird bei allen Objekten vom Typ Saeugetier die überlagernde Version der Methode getArt() aufgerufen, bei allen Objekten vom Typ Lebewesen die Ursprungsversion der Methode getArt().

Dieses Konzept wird als Dynamisches Binden bezeichnet.

Dabei hat die überlagernde Methode immer Vorrang. Welche Methodenversion aufzurufen ist, wird bestimmt, indem die Vererbungslinie des aktuellen Objekts zurückverfolgt wird und die nächstliegende Methodenversion identifiziert wird. Diese wird dann über das Objekt aufgerufen.

Performance-Verschlechterung durch Überlagern von Methoden

Da beim Dynamischen Binden erst zur Laufzeit entschieden werden kann, welche Version einer überlagerten Methode aufgerufen werden soll, muss der Compiler zusätzlichen Code generieren. Dieser zusätzliche Code führt zu einer Verschlechterung der Performance von Java-Anwendungen. Dies sollte beim Überlagern von Methoden mit bedacht werden.

Um die Performance zu verbessern, sollten so oft wie möglich statische Methodenaufrufe anstelle von dynamischen Aufrufen verwendet werden.

Dies kann der Java-Entwickler sicherstellen, indem er mit Hilfe von zusätzlichen Attributen dafür sorgt, dass Methoden nicht dynamisch interpretiert werden.

Mit folgenden Methodendeklarationen wird sichergestellt, dass Methoden nicht überlagert werden können:

  • Methoden nicht sichtbar machen – Methoden, die als private deklariert werden, sind in abgeleiteten Klassen nicht sichtbar und können daher nicht überlagert werden.
  • Methoden unveränderbar machen – Werden Methoden als final deklariert, ist dies eine explizite Anweisung, dass sie nicht überlagert werden sollen.
  • Methoden statisch machen – Methoden, die als static deklariert werden, existieren unabhängig von Instanzen und können nicht in verschiedenen Varianten vorliegen.

Verdeckte Methoden mit Hilfe des Schlüsselworts super aufrufen

Android Apps Programmieren Online-Kurs

Unser großes
Android Online-Kurs
Gesamtpaket



Weitere Infos

Durch die Methodenüberlagerung wird immer die ursprüngliche Version der Methode, die sich in der Vaterklasse befindet, überdeckt.

Der normale Methodenaufruf bezieht sich somit immer auf die überlagernde Methodenversion der aktuellen Instanz.

Möchte man bereits vorhandene Funktionalität wiederverwenden, ist es aber notwendig die verdeckten Methoden der Vaterklasse (Superklasse) aufrufen zu können.

Dies ist in Java mit Hilfe des Ausdrucks super.Methodenname() möglich. Dabei wird die Methode der Vaterklasse aufgerufen, die sogenannte Superklassen-Methode.

Das Mehrfachanwenden des super-Schlüsselworts (kaskadieren), wie bspw. super.super.Methodename(), ist nicht erlaubt. Somit kann mit super immer nur auf die überlagerte Methode der Vaterklasse zugegriffen werden und nicht auch auf dessen Vaterklasse.

Um das Schlüsselwort super richtig einsetzen zu können, ist es wichtig die Unterschiede zwischen den Schlüsselwörtern super und this verstanden zu haben.


Das this-Schlüsselwort wird in Methoden oder Konstruktoren von Instanzen benutzt. Dabei fungiert this als eine Referenz auf das Objekt über das die Methode oder der Konstruktor aufgerufen wurde. Man kann mit this auf jedes Instanzmerkmal der aktuellen Instanz aus den Methoden bzw. Konstruktoren dieser Instanz heraus zugreifen.

Das super-Schlüsselwort verweist auf die Superklasse (Vaterklasse) der aktuellen Instanz. Mit Hilfe von super kann auf die Methoden und Variablen der Superklasse der aktuellen Instanz zugegriffen werden. Falls eine Methode die Methode der Superklasse überlagert (überschreibt), kann die überlagerte Methode der Superklasse mit dem super-Schlüsselwort aufgerufen werden.

Das folgende Code-Beispiel verdeutlicht die Unterschiede zwischen den Schlüsselwörtern this und super.

Beispielanwendung für die Schlüsselwörter this und super in Java

/*
* Beispielanwendung für die Schlüsselwörter this und super in Java
*/

public class SuperAndThisTest
{
  public static void main (String[] args)
  {
     Subclass sub = new Subclass(19, 84);
     sub.printMethod();	
  }
}


class Superclass 
{
  int x, y;

  public Superclass(int x, int y) 
  {
    this.x = x;
    this.y = y;
  }

  public void printMethod() 
  {
    System.out.println("\nAusgegeben in der Superclass.");
  }
}


class Subclass extends Superclass 
{ 
  int x;
  int y = super.y - 3;

  public Subclass(int x, int y) 
  {
    super(x, y);  // ruft den Konstruktor der Superclass auf
  }

  public void printMethod() // überlagert printMethod in Superclass
  { 
    int y = 80;

    super.printMethod();  // ruft printMethod der Superclass auf
    
    System.out.println("Ausgegeben in der Subclass.\n");

    System.out.println("Variable x       = " + x);
    System.out.println("Variable this.x  = " + this.x);
    System.out.println("Variable super.x = " + super.x);

    this.x = 20; // in diesem Fall identisch mit x = 20; 
    System.out.println("\nVariable this.x  = " + x);
    System.out.println("Variable super.x = " + super.x);

    System.out.println("\nVariable y       = " + y);
    System.out.println("Variable this.y  = " + this.y);
    System.out.println("Variable super.y = " + super.y);
  }
}

Neben Methoden können auch Variablen überlagert werden. Im oberen Quelltext gibt es mehrere Überlagerungen der Variablen x und y. Die Variable y wird sogar dreifach überlagert.

In Zeile 58 wird auf die Variable y der Methode printMethod() zugegriffen.
In Zeile 59 wird auf die Variable y der Klasse Subclass mit Hilfe von this.y zugegriffen.
In Zeile 60 wird auf die Variable y der Vaterklasse Superclass mit Hilfe von super.y zugegriffen.

Starten wir nun die Beispielanwendung. In der unten abgebildete Kommandozeilenausgabe ist die resultierende Textausgabe dargestellt:

Java Methoden überlagern: Das this- und super-Schlüsselwort

Java Methoden überlagern: Das this– und super-Schlüsselwort – Ausgabe der Beispielanwendung


Comments 3

  1. Hallo,

    meine Frage bezieht sich auf das zweite Beispiel:
    Beide Konstruktoren beihnhalten Instanzvariablen mit gleichem Namen und offensichtlich existieren die Instanzvariablen ja auch zweimal, this.x und super.x . Bedeutet das, dass auch zwei Objekte erzeugt werden?

    1. Post
      Author

      Hallo Chris,

      ja so ist es. Jede Klasse hat ihre eigenen Instanzvariablen. In dem zweiten Beispiel sind die Variablennamen der Instanzvariablen gleich, dennoch sind die Variablen unterschiedlich.

      Mit this.x greift man auf die Instanzvariable x des SubClass-Objekts zu und mit super.x greift man auf die Instanzvariable x des SuperClass-Objekts zu. Beide Variablen tragen den selben Namen, gehören aber zu unterschiedlichen Objekten.

      Mit den Schlüsselwörtern this und super kann man zwischen ihnen unterscheiden. Man kann sich das wie Vor- und Nachname vorstellen. Man stelle sich zwei Personen vor: Lars Müller und Lars Fischer. Über den Vornamen kann man sie nicht unterscheiden, aber mit Müller.Lars und Fischer.Lars kann man sie gezielt ansprechen.

      Viele Grüße,
      Chris

  2. Pingback: Vererbung und Polymorphismus in Java

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.