Beispiel Polymorphie, Tier hund = new Hund();

texs89

Mitglied
Ich verstehe folgendes Beispiel für Polymorphie nicht. Welchen Vorteil hat es den Hund als Tier zu deklarieren?

Java:
Tier hund = new Hund();

statt

Code:
Hund hund = new Hund();

Ich dachte das ist damit die Methode untersuchen in der Klasse Tierarzt auch sowohl die Katze als auch den Hund als Parameter akzeptiert, weil eigentlich werden ja nur Tiere als Parameter akzeptiert und keine Hunde. Ich habe aber festgestellt, dass die Methode auch Hunde und Katzen akzeptiert die mit Hund bzw. Katze deklariert wurden (wahrscheinlich weil eben beide von Tier abgeleitet sind).

Dann verstehe ich aber nicht welchen Vorteil diese Deklaration haben soll?

Code:
Tier hund = new Hund();

Vielleicht kann einer helfen, danke!


Code:
class Tierarzt {
    public void untersuchen(Tier tier) {
        System.out.println("Das Tier wurde untersucht.");
}

class Tier {
    public void sprechen() {
        System.out.println("Das Tier spricht.");
    }
}

class Hund extends Tier {
    public void sprechen() {
        System.out.println("Wuff!");
    }
}

class Katze extends Tier {
    public void sprechen() {
        System.out.println("Miau!");
    }
}

public class TestTier {

    public static void main(String[] args) {
        Tier hund = new Hund();
        Tier katze = new Katze();

        hund.sprechen();
        katze.sprechen();

        Tierarzt tierarzt = new Tierarzt();
        tierarzt.untersuchen(hund);
        tierarzt.untersuchen(katze);

    }
}
 
Für lokale Variablen innerhalb von Methoden, die du auch gleich mit einer neuen Instanz einer gegebenen Klasse C initialisierst, sehe ich keinen Vorteil, nicht auch C als Typ der Variable zu deklarieren.
Bei "neueren" Java-Versionen kannst du hier sogar auch einfach var schreiben statt C und das Ergebnis ist dasselbe -> eine Variable, deren statischer/deklarierter Typ gleich dem Typ des new-Ausdrucks ist.
 
Ich möchte die gute Antwort von httpdigest noch etwas erweitern und hoffe, dass ich damit nicht verwirre.

Wenn man Methoden klein hält (was so sein sollte), dann ist es so, wie @httpdigest sehr schön ausgeführt hat.

Wenn Wird eine Variable im Laufe seiner Lebenszeit neu belegt, dann kann es wichtig sein. Sauge ich mir einfach einmal etwas aus den Fingern:

Java:
public void kaufeInTierhandlung() {
    var zuKaufendesTier = waehleMaus();
    if (getMyMoney() > getPriceOfHorse()) {
        zuKaufendesTier = waehlePferd();
    }
    bezahle(zuKaufendesTier);
}

Also eine Logik a.la.: wir müssen ein Tier kaufen. Daher wählen wir zuerst das Billigste aus - das sei hier mal eine Maus. Wenn unser Geld für ein Pferd reichen sollte, dann wählen wir ein Pferd aus. Und dann bezahlen wir das ausgewählte Tier.

Hier haben wir statt einem Typ einfach das var gewählt. Wenn waehleMaus eine Maus zurück gibt, dann wäre es das Gleiche wie:
Maus zuKaufendesTier = waehleMaus();

==> Das führt aber natürlich zu einem Problem, denn wir versuchen da später ein Pferd zuzuweisen. Und natürlich ist ein Pferd keine Maus. Daher würde der Code so, wie er oben geschrieben wurde, nicht übersetzen. Das ist also eine typisches Beispiel, wo es Sinn machen kann, dass man ein Interface oder eine Superklasse angibt (hier, weil man es schlicht muss).

Der Die wichtigen Punkte aus meiner Sicht sind hier:
a) Kommt es im Rahmen der Lebenszeit ggf. dazu, dass hier der Variable etwas Neues zugewiesen wird? Und wenn ja: Wie wahrscheinlich ist es, dass der Typ hier abweicht?
b) Wie wahrscheinlich ist es, dass es im Rahmen von weiteren Entwicklungen zu Veränderungen kommen kann?


Das einfach nur einmal zu dem eigentlichen Kernpunkt. Ich würde aber gerne noch etwas abweichen mit Punkten, die dieses Thema etwas berühren:

1. final Schlüsselwort: Wenn eh nicht geplant ist, dass eine Variable ein neuen Wert zugewiesen bekommt, dann kann man die Variable auch direkt final machen. Das würde dann den Punkt a) von oben z.B. auch etwas abschwächen direkt eliminieren.

Das wäre bei Deinem Code z.B.:
Java:
    public static void main(final String[] args) {
        final Hund hund = new Hund();
        final Katze katze = new Katze();

Ich möchte dies hier nur kurz in den Raum stellen, ohne es tiefer zu begründen oder zu diskutieren. Viele machen sowas nicht, es gibt aber entsprechende Regeln bei statischen Codeanalyse-Tools (die man aber natürlich auch nicht aktivieren muss!)
Aber man erkennt:
  • Parameter sind final. Hintergrund ist, dass diese halt eine Eingabe sein sollen. Da wird kein Zwischenergebnis gespeichert oder so.
  • Variablen, die nur einmal zugewiesen werden (bei der Deklaration oder bei Instanzvariablen im Konstruktor) können final sein, damit diese Absicht auch vom Entwickler deutlich wird und erzwungen wird.

2. Factory Methoden: Dieses entwickeln gegen Interfaces (oder Superklassen) macht natürlich wenig Sinn bei einer Zeile wie:
Tier tier = new Hund();
Um dann in so einer Klasse z.B. rein gegen Tier entwickeln zu können, könnte man die Erzeugung der Instanz mit new (was natürlich nur gegen eine konkrete Klasse geht) in eine separate Methode oder Klasse verschieben. Das macht es dann deutlich einfacher, hier Änderungen vorzunehmen. Das will ich aber einfach einmal bei diesem allgemeinen Hinweis belassen nur um diese Idee schon einmal bekannt zu machen. Das im Detail jetzt zu erläutern dürfte zu weit gehen und vermutlich zu sehr verwirren. (Wenn das interessiert: Ein Stichwort, das man sich hier tiefer ansehen könnte wäre Dependency Injection (DI) oder Inversion of Control (IoC) - da ist das dann sehr stark ausgebaut / basiert mit auf dieser Idee verwendet).

Das einfach einmal als kleine, weitere Ausführung in der Hoffnung dass ich vor allem mit dem ersten Teil etwas weitergeholfen habe beim Verständnis. Der zweite Teil war hoffentlich nicht zu verwirrend - und soll nur als Ausblick dienen, in welche Richtung das noch später gehen könnte.

Edit: Paar Rechtschreibfehler sowie ein paar Formulierungen angepasst.
 
Zuletzt bearbeitet:
Ich möchte die gute Antwort von httpdigest noch etwas erweitern und hoffe, dass ich damit nicht verwirre.

Wenn man Methoden klein hält (was so sein sollte), dann ist es so, wie @httpdigest sehr schön ausgeführt hat.

Wenn Wird eine Variable im Laufe seiner Lebenszeit neu belegt, dann kann es wichtig sein. Sauge ich mir einfach einmal etwas aus den Fingern:

Java:
public void kaufeInTierhandlung() {
    var zuKaufendesTier = waehleMaus();
    if (getMyMoney() > getPriceOfHorse()) {
        zuKaufendesTier = waehlePferd();
    }
    bezahle(zuKaufendesTier);
}

Also eine Logik a.la.: wir müssen ein Tier kaufen. Daher wählen wir zuerst das Billigste aus - das sei hier mal eine Maus. Wenn unser Geld für ein Pferd reichen sollte, dann wählen wir ein Pferd aus. Und dann bezahlen wir das ausgewählte Tier.

Hier haben wir statt einem Typ einfach das var gewählt. Wenn waehleMaus eine Maus zurück gibt, dann wäre es das Gleiche wie:
Maus zuKaufendesTier = waehleMaus();

==> Das führt aber natürlich zu einem Problem, denn wir versuchen da später ein Pferd zuzuweisen. Und natürlich ist ein Pferd keine Maus. Daher würde der Code so, wie er oben geschrieben wurde, nicht übersetzen. Das ist also eine typisches Beispiel, wo es Sinn machen kann, dass man ein Interface oder eine Superklasse angibt (hier, weil man es schlicht muss).

Der Die wichtigen Punkte aus meiner Sicht sind hier:
a) Kommt es im Rahmen der Lebenszeit ggf. dazu, dass hier der Variable etwas Neues zugewiesen wird? Und wenn ja: Wie wahrscheinlich ist es, dass der Typ hier abweicht?
b) Wie wahrscheinlich ist es, dass es im Rahmen von weiteren Entwicklungen zu Veränderungen kommen kann?


Das einfach nur einmal zu dem eigentlichen Kernpunkt. Ich würde aber gerne noch etwas abweichen mit Punkten, die dieses Thema etwas berühren:

1. final Schlüsselwort: Wenn eh nicht geplant ist, dass eine Variable ein neuen Wert zugewiesen bekommt, dann kann man die Variable auch direkt final machen. Das würde dann den Punkt a) von oben z.B. auch etwas abschwächen direkt eliminieren.

Das wäre bei Deinem Code z.B.:
Java:
    public static void main(final String[] args) {
        final Hund hund = new Hund();
        final Katze katze = new Katze();

Ich möchte dies hier nur kurz in den Raum stellen, ohne es tiefer zu begründen oder zu diskutieren. Viele machen sowas nicht, es gibt aber entsprechende Regeln bei statischen Codeanalyse-Tools (die man aber natürlich auch nicht aktivieren muss!)
Aber man erkennt:
  • Parameter sind final. Hintergrund ist, dass diese halt eine Eingabe sein sollen. Da wird kein Zwischenergebnis gespeichert oder so.
  • Variablen, die nur einmal zugewiesen werden (bei der Deklaration oder bei Instanzvariablen im Konstruktor) können final sein, damit diese Absicht auch vom Entwickler deutlich wird und erzwungen wird.

2. Factory Methoden: Dieses entwickeln gegen Interfaces (oder Superklassen) macht natürlich wenig Sinn bei einer Zeile wie:
Tier tier = new Hund();
Um dann in so einer Klasse z.B. rein gegen Tier entwickeln zu können, könnte man die Erzeugung der Instanz mit new (was natürlich nur gegen eine konkrete Klasse geht) in eine separate Methode oder Klasse verschieben. Das macht es dann deutlich einfacher, hier Änderungen vorzunehmen. Das will ich aber einfach einmal bei diesem allgemeinen Hinweis belassen nur um diese Idee schon einmal bekannt zu machen. Das im Detail jetzt zu erläutern dürfte zu weit gehen und vermutlich zu sehr verwirren. (Wenn das interessiert: Ein Stichwort, das man sich hier tiefer ansehen könnte wäre Dependency Injection (DI) oder Inversion of Control (IoC) - da ist das dann sehr stark ausgebaut / basiert mit auf dieser Idee verwendet).

Das einfach einmal als kleine, weitere Ausführung in der Hoffnung dass ich vor allem mit dem ersten Teil etwas weitergeholfen habe beim Verständnis. Der zweite Teil war hoffentlich nicht zu verwirrend - und soll nur als Ausblick dienen, in welche Richtung das noch später gehen könnte.

Edit: Paar Rechtschreibfehler sowie ein paar Formulierungen angepasst.
Vielen Dank, das Beispiel mit waehleMaus und waehlePferd hat mir sehr weitergeholfen, da macht es tatsächlich Sinn die Deklaration allgemein zu halten.
 

Zurück
Oben