Java Polymorphie?

3 Antworten

Vom Fragesteller als hilfreich ausgezeichnet

Polymorphie in der OOP beschreibt den Umstand, dass ein Bezeichner einen anderen Datentyp annehmen kann. Mögliche Datentypen (neben dem eigenen) sind aber nur jene, welche in der Vererbungshierarchie darunter liegen, sich also wesentliche Eigenschaften mit dem Basistyp teilen.

So gilt:

class Animal {}
class Elephant extends Animal {}
class Car {}

Animal indianElephant = new Animal();
indianElephant = new Elephant();
indianElephant = new Car(); // ERROR

Führe ich nun noch ein größeres Beispiel an:

interface IPrintable {
  void print();
  void setText(String text);
}

class Text implements IPrintable {
  protected String text;
  
  @Override
  public void print() {
    System.out.println(text);
  }

  @Override
  public void setText(String text) {
    this.text = text;
  }
}

class GroupedText extends Text {
  
  @Override
  public void print() {
    System.out.println("(" + text + ")");
  }
}

// main:
IPrintable[] texts = new IPrintable[]{ new Text(), new GroupedText() };

for (IPrintable text : texts) {
  text.setText("Hello");
  text.print();
}

Das Interface IPrintable kann die zwei Datentypen in sich aufnehmen, die in der Vererbungshierarchie unter dem Interface liegen, sich also mit dem Interface Eigenschaften und Verhaltensweisen teilen. Genauso wäre aber auch folgendes möglich:

Text someText = new GroupedText();

Weil GroupedText von Text ableitet, kann dieser Bezeichner auf das Objekt des Subtyps zeigen.

Zu beachten ist, dass der Bezeichner nun nur noch die Methoden kennt, die der Basistyp kennt. Wenn Text also bspw. eine Methode changeColor implementieren würde, wäre diese über den Bezeichner text (siehe Beispiel von oben) dennoch nicht aufrufbar. Dieser orientiert sich nämlich nach dem Bauplan, den das Interface vorgibt bzw. dem eigenen Datentyp, nicht dem des Objekts (Text).

Um auf das wirkliche Objekt, auf welches referenziert wird, wieder zugreifen zu können, müsste man daher einen Typecast vornehmen:

text.changeColor("red"); // ERROR

Text realText = (Text) text;  // typecast needed
realText.changeColor("red");

// or in short:
((Text) text).changeColor("red");

Dies zeigt: Das konkrete Objekt, welches einem Bezeichner zugewiesen wird, weiß stets über all seine Methoden und Zustände bescheid, auch wenn es der Bezeichner selbst nicht tut.

Die letztgenannte Eigenheit der Polymorphie (ein Objekt kennt seine eigenen Zustände und Methoden) soll nun noch im Bezug auf die späte Bindung erklärt werden, welche hier bei den Methodenaufrufen in der Schleife auftritt. 

IPrintable[] texts = new IPrintable[]{ new Text(), new GroupedText() };

for (IPrintable text : texts) {
  text.setText("Hello");
  text.print();
}

Bei jedem Methodenaufruf wird die Vererbungskette durchlaufen und die letzte / unterste Definition von print verwendet. 

  • Beim 1. Durchlauf zeigt der Bezeichner auf die konkrete Instanz von Text und ruft von dieser Klasse die Methode print auf.
  • Beim 2. Durchlauf zeigt der Bezeichner auf die konkrete Instanz von GroupedText und ruft von dieser Klasse die Methode print auf. Würde GroupedText die Methode nicht überschreiben, würde die Definition aus dem Basistyp Text genutzt werden.

So viel zur Polymorphie, wie sie bei vielen Sprachen (C#, Java, ...) definiert ist. In einigen Sprachen (z.B. Objective-C) geht das polymorphe Verhalten sogar noch weiter, über die Klassengrenzen hinaus.

regex9  18.01.2018, 21:46

Ich habe gerade gemerkt, dass der Abschnitt nach dem untersten Code-Block etwas uneindeutig sein könnte.

Bei jedem Methodenaufruf wird die Vererbungskette durchlaufen und die letzte / unterste Definition von print verwendet. 

Der erwähnte Durchlauf hat nichts mit der Schleife zu tun, die ich im obigen Beispiel implementiert habe. Die einzelnen Punkte (1. Durchlauf, 2. Durchlauf) hingegen beziehen sich wiederum auf die Schleife bzw. auf das Ergebnis, welches je Iterationsschritt berechnet wird.

Folgend gebe ich nochmal ein einfaches Beispiel von später Bindung:

class Human {
  public void walk() {
    System.out.println("Walks.");
  }
}

class Male extends Human {
}

class Boy extends Male {
}

class Female extends Human {
  @Override
  public void walk() {
    System.out.println("Walks like only a beauty can walk.");
  }
}

class Girl extends Female {
  @Override
  public void walk() {
    System.out.println("Jumps over grass and flowers");
  }
}

// in main:
Human man = new Male();
Human woman = new Female();
Human boy = new Boy();
Human girl = new Girl();

man.walk(); // Walks.
woman.walk(); // Walks like only a beauty can walk.
boy.walk(); // Walks.
girl.walk(); // Jumps over grass and flowers.

Weder in der Klasse Male noch in der Klasse Boy wird die geerbte Methode überschrieben. Daher wählt das Objekt bei Aufruf die geerbte Implementation aus Human. Bei einem Objekt der Klasse Girl ist es anders, dort wird die objekteigene Implementation verwendet. Wäre diese nicht vorhanden, würde die Implementation aus der Klasse darüber (Female) genutzt werden. Würde dort die Methode ebenfalls nicht überschrieben werden, fiele die Wahl auf die Methode aus Human.

0

Polymorphie heißt, dass ein Objekt mehrere Methoden der gleichen Signatur hat. In Java ist das nur mithilfe von Vererbung möglich. Beispiel:

Die Klasse Auto hat die Methode tempoÄndern(int tempo).

class Auto {
  void tempoÄndern(int tempo) {
    System.out.println("Auto fährt "+ tempo +" km/h");
  }
}

Die Klasse Cabrio hat ebenfalls die Methode tempoÄndern(int tempo). Da ein Cabrio auch ein Auto ist, ist die Klasse Cabrio eine Unterklasse von Auto.

class Cabrio extends Auto {
  void tempoÄndern(int tempo) {
    System.out.println("Cabrio fährt "+ tempo +" km/h");
  }
}

Jede Instanz/jedes Objekt der Klasse Cabrio erbt also die Attribute und Methoden der Klasse Auto. Ein Cabrio hat die Methode tempoÄndern(int tempo) also doppelt. Wenn eine Methode aufgerufen wird, die es mehr als einmal gibt, wählt Java immer die Methode am weitesten unten in der Vererbungshierarchie. Die Methode in Cabrio überschreibt also die Methode in Auto.

Ein Cabrio kann aber trotzdem die Methode in der Superklasse Auto aufrufen, nämlich mittels des Schlüsselwortes super.

// rufe tempoÄndern(int tempo) in Superklasse auf:
super.tempoÄndern(15);

Eine häufige Anwendung davon ist, dass eine Methode, die eine andere Methode überschreibt, die überschriebene Methode aufruft und dann noch etwas anderes macht. Die überschriebene Methode wird damit also nicht komplett ersetzt, sondern lediglich ergänzt. Beispiel:

class Cabrio extends Auto {
  void tempoÄndern(int tempo) {
    super.tempoÄndern(tempo);
    System.out.println("WRUMMMM!");
  }
}

Polymorphie hat übrigens nichts mit Methoden überladen (method overloading) zu tun, da beim Überladen von Methoden diese nicht die gleiche Signatur haben. Eine Methodensignatur besteht aus

  • Name der Methode
  • Liste der Methodenparameter (mit Datentyp)

Wenn du also eine Methode tempoÄndern(int tempo) und eine Methode tempoÄndern(int tempo, int dauer) in der gleichen Klasse hast, handelt es sich um überladene Methoden, aber NICHT um Polymorphie; Bei Polymorphie muss die Signatur übereinstimmen.

Woher ich das weiß:Studium / Ausbildung – Informatikstudium
VeryBestAnswers  26.12.2017, 23:25

PS: Ich habe einen Fehler gemacht. Methoden überladen ist eine Form von Polymorphismus, auch wenn dies zu Compile-Zeit geschieht.

0

Ich versuche es einfach zu erklären. Ein Auto und ein Motorboot sind beides zwar Fahrzeuge, haben als einige Dinge gemeinsam (machen Krach, man kann damit angeben, mit beiden kann man sich totfahren), aber sie bremsen auf verschiedene Art und Weise. Bei einem Auto werden bspw. die Reifen entsprechend mechanisch am weiterdrehen gehindert, beim Motorboot muss die Motorschraube in die entgegengesetzte Richtung drehen. Du möchtest das bei Auto und Motorboot, die eben beide von der Mamaklasse Fahrzeug erben, die Methode für das Bremsen eben Bremsen heißt. Mit Polymorphie kannst du zwei inhaltlich unterschiedlich Methoden namens Bremsen haben und der Compiler sorgt dann schon das entsprechende Methode korrekt zugeordnet wird.

Ich kenne das das eine oder andere Spiel wo das mit der Polymorphie nicht so gut funktioniert hat, beispielsweise Mercenaries 2, wo man sich zuweilen durch einen Bug schwimmend über eine Strand bewegt. Denn auch das kannst du mit Polymorphie, das wenn du ein Objekt hast, was auf verschiedene Art sich wie in diesem Fall fortbewegen kann, dafür zu Sorgen das die richtige Fortbewegungs-Methode bei entsprechender Situation gewählt wird.