gleicher Name in der Basisklasse und in der Subklasse

ernst

Top Contributor
Hallo allerseits,
siehe Quellcode unten
Mit
((Vater)this).name
wird auf das Attribut name des Vaters zugegriffen und nicht auf den des Sohnes!
Warum wird aber mit
((Vater)this).print();
nicht auf die Methode des Vaters zugeriffen, sondern auf die
print() des Sohnes ?

Das ist mir absolut unverständlich.

mfg
Ernst


Code:
public class Startklasse {
    public static void main(String[] args){
            Sohn s = new Sohn("klaus", "walter");
            s.print();
    }
}

class Vater{
    String name;

        public void print(){
            System.out.println("Ich bin der Vater");
        }
      
    public Vater(String name){
            this.name=name;          
    }
}

class Sohn extends Vater{
    String name;
  
        public void print(){
            System.out.println("Vater_name="+((Vater)this).name);
            ((Vater)this).print();
        }
      
    Sohn(String name, String vname){
            super(vname);          
            this.name=name;          
    }
}
 
Zuletzt bearbeitet:
Weil nur Methodenaufrufe polymorph sind bzw. überschrieben werden können, nicht aber Instanzvariablenzugriffe. Hier "versteckst" du die Variable der Elternklasse nur durch die Redeklaration einer Variablen mit demselben Namen in einer Kindklasse und brauchst den qualifizierten Zugriff auf die Variable durch Cast bzw. Nennung des Typs.
Es ist also egal, zu welchem konkreten Zieltyp du einen Aufruf einer überschriebenen, nicht-statischen Methode castest: Es entscheidet immer nur der Laufzeittyp des Empfängerobjektes, auf dem du die Methode aufrust, welche konkrete überschriebene Methode letztlich aufgerufen wird - nicht der Compilezeittyp des Ausdrucks, auf dem du die Methode aufrust.
 
entscheidet immer nur der Laufzeittyp des Empfängerobjektes, auf dem du die Methode aufrust, welche konkrete überschriebene Methode letztlich aufgerufen wird - nicht der Compilezeittyp des Ausdrucks, auf dem du die Methode
Vielen Dank für die Antwort.
Aber woher weiß man, wer der
"Laufzeittyp des Empfängerobjektes, auf dem du die Methode aufrust"
ist?
Kannst du mir das konkret an dem Beispiel zeigen ?

mfg
Ern
 
Das weiß man eben nicht - das wird eben erst zur Laufzeit festgelegt.

3 Klassen
Java:
public class A {
  public doSomething() {}
}

public class B extends A {
  public doSomething() {}
}

public class C extends A {
  public doSomething() {}
}

Wenn ich jetzt folgenden Code habe:


Java:
A meineVariable = holeMirEinObjekt();
meineVariable.doSomething();

Dann hängt es davon, was holeMirEinObjekt zurückgibt - ob es ein A, B oder C Objekt zurückgibt. Das sieht man dem Code aber nicht an.

Evtl. ist das z.B. Abhängig von der Uhrzeit etc. Die Idee von Polymorphismus ist eben, dass man es nicht wissen muss (bzw. gar nicht soll). Beispiel die Klasse List aus Java (bzw. genauer das Interface, aber für die Erklärung ist das gerade egal).

List bietet eine Methode size() an, die die Anzahl der Elemente zurückliefert. Wenn ich die Aufrufe, ist mir als Aufrufer vollkommen egal, welche konkrete Ableitung von List ich in der Hand habe - ob das eine ArrayList ist, eine LinkedList, einen Stack oder sonst was. Java sorgt zur Laufzeit dafür, dass die korrekte Implementierung aufgerufen wird.
 
Erstmal vielen Dank für deine Antwort.

Ich beziehe mich im Folgenden auf mein Anfangsbeispiel:
>
>Aber woher weiß man, wer der
>"Laufzeittyp des Empfängerobjektes, auf dem du die Methode aufrust"
>ist?
>

Meine Antwort:
Der Laufzeittyp ist Sohn
Das Empfängerobjekt ist s
Stimmt das ?

Wenn s das Empfängerobjekt ist, dann wird
s.print aufgerufen.
Also wird der folgende Programmcode abgearbeitet:
System.out.println("Vater_name="+((Vater)this).name);
((Vater)this).print();

Mir ist allerdings nicht klar, warum das Empfängerobjekt s
dann nicht
((Vater)this).print();
also (da this hier gleich s ist)
((Vater)s).print();
aufrufen kann.

mfg
Ern
 
Meine Antwort:
Der Laufzeittyp ist Sohn
Das Empfängerobjekt ist s
Stimmt das ?
Wenn du den Code hier meinst:
Java:
Sohn s = new Sohn("klaus", "walter");
s.print();
Dann ist der Laufzeittyp des Objektes, auf dem print() aufgerufen wird, Sohn. Korrekt.
Objekte allerdings haben keine Bezeichner in Java. Also das Empfängerobjekt ist nicht `s`, sondern:
Das Objekt, auf dem die `print()` Methode aufgerufen wird, befindet sich in der lokalen Variablen `s`.
Das Auswerten dieser Variablen durch den Ausdruck `s` liefert das Objekt (vom Typ `Sohn`) und der Aufruf der Methode per `.print()` ruft dann eben auf diesem Objekt die Methode `print()` auf.

Mir ist allerdings nicht klar, warum das Empfängerobjekt s
dann nicht
((Vater)this).print();
also (da this hier gleich s ist)
((Vater)s).print();
aufrufen kann.
Wieso nicht? Tut es doch. Erstmal ist der Ausdruck ((Vater)this) hier völlig unnötig. Du machst einen Upcast zu einem Typ, den das Objekt sowieso selber schon hat und rufst dann eine überschriebene Methode auf. Und der Aufruf einer überschriebenen Methode ruft _immer_ die am meisten überschriebene Methode ausgehend vom Laufzeittyp des Empfängerobjektes auf.
Vielleicht meinst du eher: `super.print()`?

Oder vielleicht nennst du auch erstmal das eigentliche Problem bzw. die zu lösende Aufgabe?
 
Also Du hast einen Apfel. Und es gibt diverse Behälter:
Einen allgemeinen Behälter,der beliebige Objekte (auch Äpfel) beinhalten kann.
Eine Obsttasche - die nimmt nur Obst. Also auch Äpfel.
Eine Apfelverpackung - da kann man nur Äpfel rein tun.

Wenn Du nun die Methode "essen" vom Apfel aufrufst: Isst du etwas anderes abhängig davon, ob der Apfel nun aus der Apfelverpackung, Obsttasche oder dem allgemeinen Behälter kommt?

Die Aufbewahrung ist jetzt die Variable.
Dieses (Vater) s kannst Du auch ähnlich sehen. Du hast zwar keinen wirklichen "Behälter" aber sieh es einfach einmal als eine Art: Nimm einen "Vater" Behälter und pack es dann da rein und arbeite dann weiter mit diesem Vater Behälter.

Wenn man das auf mein Beispiel überträgt: Das Objekt kommt aus einem allgemeinen Behälter. Da ich aber auf jeden Fall einen Apfel essen will, packe ich es erst ein einen Apfel Behälter. Sollte ich keinen Apfel bekommen haben, dann passt das Teil einfach nicht und Du bekommst eine Exception.

Also generell: Egal was Du mit dem Gegenstand machst: Du machst es immer mit dem Gegenstand. Ein Apfel bleibt ein Apfel, auch wenn es in einen anderen Behälter kommt.
 
Das ist mir absolut unverständlich.
Das was Du willst ist:
Java:
class Sohn extends Vater {
    String name;

    public void print() {
        System.out.println("Vater_name=" + ((Vater) this).name);
        super.print();
    }

    Sohn(String name, String vname) {
        super(vname);
        this.name = name;
    }
}
Dein Code hat eine endlose Rekursion zur Folge. Weil Du in der Methode print() die Methode noch einmal rekursiv aufrufst. Mit this verweist Du wieder auf das Objekt selbst --> Es wird die Methode von Sohn aufgerufen. Der Cast auf den Type Vater funktioniert, weil Sohn ja von Vater erbt.
Java:
public void print(){ System.out.println("Vater_name="+((Vater)this).name); ((Vater)this).print(); }
😉
 
Und der Aufruf einer überschriebenen Methode ruft _immer_ die am meisten überschriebene Methode ausgehend vom Laufzeittyp des Empfängerobjektes auf.
Was ist "die am meisten überschriebene Methode" ?

Mir ist immer noch nicht die Vorgehensweise klar, welches print() ermittelt wird:
Die Variable s hat den Datentyp Sohn und Datentyp Vater.
Problem1:
Welcher Datentyp wird genommen?

Problem2:
Wenn es der in der Hierarchie am weitesten oben stehende Datentyp ist (hier also Vater), dann gibt es wieder (auf die Hierarchie bezogen) 2 Hierarchien, die gleich oder darunter liegend sind, nämlich Vater und Sohn.
Von welcher wird nun das zugehörige print() ausgewählt.

Ich will eben nur den "Algorithmus" dafür wissen, welches print() ausgewählt wird.

mfg
Ern
 
Was ist "die am meisten überschriebene Methode" ?

Mir ist immer noch nicht die Vorgehensweise klar, welches print() ermittelt wird:
Die Variable s hat den Datentyp Sohn und Datentyp Vater.
Problem1:
Welcher Datentyp wird genommen?

Problem2:
Wenn es der in der Hierarchie am weitesten oben stehende Datentyp ist (hier also Vater), dann gibt es wieder (auf die Hierarchie bezogen) 2 Hierarchien, die gleich oder darunter liegend sind, nämlich Vater und Sohn.
Von welcher wird nun das zugehörige print() ausgewählt.

Ich will eben nur den "Algorithmus" dafür wissen, welches print() ausgewählt wird.

mfg
Ern
Du hast die falsche Sichtweise.

Die Variable s hat den Typ Sohn => Das heißt die Aussage ist "Hier ist ein Objekt drin gespeichert, was entweder der Klasse Sohn oder einer Subklasse davon entspricht"

Damit weiß der Compiler "In dem Objekt, was in der Variable s gespeichert wird gibt es die Methode print".

Zur Laufzeit kommt der Laufzeitumgebung und stellt fest: Ah ich soll in dem Objekt, was in s gespeichert wird, die Methode print aufrufen ohne Parameter (Hint: Hier ist im Endeffekt egal welchen Typ die Variable s hat). Ok, dann schauen wir doch mal was für eine Typ das Objekt hat das drin steckt? (wäre, wenn man es programmieren würde s.getClass()). Gibt es da eine Methode print ohne Parameter? Wenn ja => Rufe sie auf. Wenn nein: Dann schau ich mal die Oberklasse an. Gibt es da eine Methode print ohne Parameter? usw. Wenn man am Ende bei Object angekommen ist und es da auch die Methode nicht gibt, wirft die Laufzeitumgebung eine NoSuchMethodException.
 
Ich will eben nur den "Algorithmus" dafür wissen, welches print() ausgewählt wird.
Der ganz genaue Algorithmus für sogenanntes polymorphes "dynamic dispatch" ist hier beschrieben: https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.4.4
Fange an zu lesen ab:
Otherwise, the invocation mode is interface, virtual, or super, and overriding may occur. A dynamic method lookup is used. The dynamic lookup process starts from a class S, determined as follows:
Der Fall "virtual" ist der Fall, der dich interessiert.
 
Zur Laufzeit kommt der Laufzeitumgebung und stellt fest: Ah ich soll in dem Objekt, was in s gespeichert wird, die Methode print aufrufen ohne Parameter (Hint: Hier ist im Endeffekt egal welchen Typ die Variable s hat). Ok, dann schauen wir doch mal was für eine Typ das Objekt hat das drin steckt? (wäre, wenn man es programmieren würde s.getClass()). Gibt es da eine Methode print ohne Parameter? Wenn ja => Rufe sie auf. Wenn nein: Dann schau ich mal die Oberklasse an. Gibt es da eine Methode print ohne Parameter? usw. Wenn man am Ende bei Object angekommen ist und es da auch die Methode nicht gibt, wirft die Laufzeitumgebung eine NoSuchMethodException.

Ok, habe ich hoffentlich soweit verstanden.
Konkret:
Beim Aufruf
s.print();
stellt die Laufzeitumgebung fest:
Es gibt im Objekt s die Methode print().
Also wird sie aufgerufen, dh. der folgende Programmcode abgearbeitet:
----------------------------------
System.out.println("Vater_name="+((Vater)this).name);
((Vater)this).print();
-------------------------------------
also wird speziell auch die folgende Anweisung abgearbeitet:
((Vater)this).print();
this ist das Objekt s, also müsste nach deiner Beschreibung ("Algorithmus")
((Vater) s ).print()
aufgerufen werden.
Das wird aber nicht gemacht.
Wo liegt mein Denkfehler ?

mfg
Ern
 
Warum erbt Sohn eigtl von Vater bei dir?

Mit einem "korrekteren" Beispiel wäre das Verständnis vll auch einfacher... Ersetze z.b. Vater durch Mensch.

Eine Vererbung ist immer eine "is a" Beziehung. Ein Sohn ist ja kein Vater...
 
Warum erbt Sohn eigtl von Vater bei dir?
Mit einem "korrekteren" Beispiel wäre das Verständnis vll auch einfacher... Ersetze z.b. Vater durch Mensch.
Eine Vererbung ist immer eine "is a" Beziehung. Ein Sohn ist ja kein Vater...
Du hast Recht.
Aber die Idee habe ich aus einem guten Buch ("Java als erste Programmiersprache" von Heinisch, Müller-Hofmann, Goll)
Sohn - Vater - Grossvater
soll einfach die Hierarche anzeigen.

mfg
Ern
 
also wird speziell auch die folgende Anweisung abgearbeitet:
((Vater)this).print();
this ist das Objekt s, also müsste nach deiner Beschreibung ("Algorithmus")
((Vater) s ).print()
aufgerufen werden.
Das wird aber nicht gemacht.
Wo liegt mein Denkfehler ?
Dein Denkfehler liegt darin, zu glauben, dass der Ausdruck:
Java:
((Vater) s ).print()
bedeutet:
"Rufe die Methode print() in Klasse Vater auf dem Objekt in `s` auf."
Das ist FALSCH!
Es wird die print() Methode aufgerufen, die in der Typhierarchie des Laufzeittypen des Objektes in `s` am weitesten überschrieben ist. Der Laufzeittyp des Objektes, das in der Variablen `s` gespeicher ist, ist immer noch Sohn, egal, ob du statisch nach "Vater" castest oder nicht. Und Sohn überschreibst die print() Methode. Somit wird also die print() Methode der Klasse Sohn auf dem Objekt in `s` aufgerufen.
Lies' dir bitte den referenzierten Abschnitt https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.4.4 durch. Dort ist haargenau beschrieben, was bei jedem Aufruf einer virtuellen/überschriebenen Methode passiert.
 
Ok, habe ich hoffentlich soweit verstanden.
Konkret:
Beim Aufruf
s.print();
stellt die Laufzeitumgebung fest:
Es gibt im Objekt s die Methode print().
Also wird sie aufgerufen, dh. der folgende Programmcode abgearbeitet:
----------------------------------
System.out.println("Vater_name="+((Vater)this).name);
((Vater)this).print();
-------------------------------------
also wird speziell auch die folgende Anweisung abgearbeitet:
((Vater)this).print();
this ist das Objekt s, also müsste nach deiner Beschreibung ("Algorithmus")
((Vater) s ).print()
aufgerufen werden.
Das wird aber nicht gemacht.
Wo liegt mein Denkfehler ?
Neben dem was schon erwähnt wurde - Der Cast ist vollkommen egal. Du casted s nach Vater. da gibt es eine Methode print, Haken dran, deswegen kompiliert der Code. Nun soll zur Laufzeit geschaut werden, was aufgerufen wird - und der stellt fest in s ist ein Objekt vom Typ Sohn gespeichert und ruft die Methode der Sohn-Klasse auf.

Java:
((Vater) s)
Ändert nicht das in s gespeicherte Objekt auf eins vom Typ Vater. ist es immer noch ein Objekt vom Typ Sohn. Nur für den Compiler wird gesagt "Behandel es wie ein Vater-Objekt, sprich, stell nur die Methoden für den Compiler zur Verfügung, die Vater bereitstellt.

Du änderst nur die Fassade - hinter der Fassade ist immer noch das gleiche Objekt.
 
Was ist "die am meisten überschriebene Methode" ?

Mir ist immer noch nicht die Vorgehensweise klar, welches print() ermittelt wird:
Die Variable s hat den Datentyp Sohn und Datentyp Vater.
Problem1:
Welcher Datentyp wird genommen?
Der Typ von dem die Variable ist.
Also:
Java:
[B]s[/B] = new Sohn();
( (Vater)[B] s[/B]).print()
Der Cast auf Vater ändert nichts am Type der Variable s.
Du musst aber zwischen dem Überdecken von Variablen und dem Überschreiben von Methoden unterscheiden.
Java:
(Vater) this).name
Greift auf dem Namen von Vater zu. Die Variable existiert für Sohn 2 x. Vater.name und Sohn.name. Sohn kennt aber nur eine print() Methode.
Durch super.print() kann man auf die print Methode von Vater zugreifen.
Normalerweise macht das Überdecken von Variablen wenig Sinn, da man durch Vererbung eine bestehende Klasse erweitern will durch neue Attribute und neue oder veränderte Methoden. Außerdem führt das zu unübersichtlichem Code welcher name ist gemeint.
Vererbung sollte immer auch semantisch stimmen.
Ein Sohn ist ein Vater ist nicht gerade sinnvoll.
 
Der Typ von dem die Variable ist.
Also:
Java:
[B]s[/B] = new Sohn();
( (Vater)[B] s[/B]).print()
Der Cast auf Vater ändert nichts am Type der Variable s.
1)
Gut, dann ändere ich den Quellcode von print() in der Klasse Sohn ab zu:
Code:
        public void print(){
            System.out.println("Vater_name="+((Vater)this).name);
            Vater x;
            x = this;
            x.print();
            //((Vater)this).print();            
        }
Jetzt ist x beim Kompilieren vom Type Vater und während der Laufzeit vom Type Sohn.
Also wird die Methode print() von der Klasse Sohn aufgerufen. Das ist mir jetzt klar.

2)
Hier nochmals der alte Quellcode von der Methode print() der Klasse Sohn:
Code:
        public void print(){
            System.out.println("Vater_name="+((Vater)this).name);
            ((Vater)this).print();            
        }
Wenn man
((Vater)this).print();
aufruft, kann man sich das so vorstellen, dass intern temporär eine "anonyme" Variable a erzeugt wird und intern
a = (Vater)this;
gemacht wird und dann
a.print();
aufgerufen wird.
Jetzt ist a beim Kompilieren vom Type Vater und während der Laufzeit vom Type Sohn.
Also wird die Methode print() von der Klasse Sohn aufgerufen.
So kann ich mir das erklären.
Ist das korrekt?

mfg
Ern
 
So kann ich mir das erklären.
Ist das korrekt?
1) this verweist immer auf das Objekt selbst.
2) der Cast von Sohn auf Vater funktioniert weil Sohn von Vater erbt
3) Trotz Zuweisung:
Java:
Vater a =(Vater)this;
verweist a immer noch auf das Sohn Objekt .
4) Sohn kennt die Methode print() von Vater nicht mehr weil diese überschrieben wurde. --> Aufruf print() von Sohn
Aber:
Die Attribute werden von Vater geerbt. Sohn kennt sowohl Vater.name als auch Sohn.name Hier wird nicht überschrieben sondern überdeckt.
Daher funktioniert ((Vater) s).name.
 
Danke für die Antwort.
Trotzdem nochmals gefragt.
War meine Erklärung korrekt?
Speziell:
Jetzt ist a beim Kompilieren vom Type Vater und während der Laufzeit vom Type Sohn.

mfg
Ern
 
Wieso nicht? Tut es doch. Erstmal ist der Ausdruck ((Vater)this) hier völlig unnötig. Du machst einen Upcast zu einem Typ, den das Objekt sowieso selber schon hat und rufst dann eine überschriebene Methode auf. Und der Aufruf einer überschriebenen Methode ruft _immer_ die am meisten überschriebene Methode ausgehend vom Laufzeittyp des Empfängerobjektes auf.
Was ist die am meisten überschriebene Methode ?

mfg
Ern
 
Was ist die am meisten überschriebene Methode ?
Der Begriff "am meisten" war hier vielleicht nicht ganz sinnvoll. Damit meine ich die Methodenimplementierung, die in der Typhierarchie des jeweiligen Laufzeittypen am "nächsten" zum Laufzeittypen ist. Wenn du also die Typhierarchie A, B extends A, C extends B hast und A und B implementieren die Methode `print()` aber C implementiert print() nicht und der Laufzeittyp des Objektes, auf dem du `print()` aufrufst C ist, dann ist die "am weitesten" überschriebene Methode in Richtung des Laufzeittypen `C` dann B.print(), weil sie in der Typhierarchie A <- B <- C am "nächsten" zum Laufzeittyp C liegt (wo ja die Suche nach der tatsächlich aufzurufenden Methode startet und die Typhierarchie nach oben wandert).
Wenn zum Beispiel auch C die Methode print() implementieren würde, dann wäre die "nächste" Methode `print()` ausgehend von C die Methode C.print(). Und wenn in der Hierarchie A <- B <- C nur A die Methode print() implementieren würde, und du auf einem Objekt vom Typ C die Methode print() aufrufst, dann ist die "nächste" Implementierung der Methode print() in A, ausgehend von C.
 
Wenn zum Beispiel auch C die Methode print() implementieren würde, dann wäre die "nächste" Methode `print()` ausgehend von C die Methode C.print(). Und wenn in der Hierarchie A <- B <- C nur A die Methode print() implementieren würde, und du auf einem Objekt vom Typ C die Methode print() aufrufst, dann ist die "nächste" Implementierung der Methode print() in A, ausgehend von C.
Ok, habe ich verstanden.
Das Programm von mir war ein Versuch auf die print() Methode der Oberklasse zuzugrifen, ohne super.print() zu benutzen.
Man könnte jetzt einwenden und sagen. Warum der Aufwand ? Nehm doch super.print().
Was ist aber wenn ich auf die print() Methode der OberOberklasse zugreifen will (in meinem Beispiel müsste ich noch die Klasse Grossvater ergänzen).
super.super.print() geht syntaktisch nicht.
Gibt es eine Möglichkeit auf die print() Methode der OberOberklasse zuzugreifen?

mfg
Ern
 
super.print() ruft die Methode auf, die "überschrieben" wurde.

Das heißt, wenn in der Oberklasse print() nicht überschrieben wurde, dann ruft super.print() die Methode der OberOberklasse auf.
Wenn aber in der Oberklasse print() überschrieben wurde, gibt es keine Möglichkeit direkt die Methode der OberOberklasse aufzurufen. Das widerspricht der Ableitung als "Is A" Prinzip. Denn die Klasse ist eine Ableitung der Oberklasse - das heißt sie muss sich eigentlich wie eine Oberklasse verhalten ggf. noch etwas spezieller. Wenn ich nun die print Methode der Oberklasse umgehe und direkt auf die der OberOberKlasse zugreife verletze ich das Prinzip - den ich sage "Ich will mich an der stelle nicht wie meine Oberklasse verhalten, aber wie meine OberOberKlasse". Und das ist selten sinnvoll - wenn man das machen will, deutet es eher auf schlechte Architektur hin.
 
Das widerspricht der Ableitung als "Is A" Prinzip. Denn die Klasse ist eine Ableitung der Oberklasse - das heißt sie muss sich eigentlich wie eine Oberklasse verhalten ggf. noch etwas spezieller. Wenn ich nun die print Methode der Oberklasse umgehe und direkt auf die der OberOberKlasse zugreife verletze ich das Prinzip - den ich sage "Ich will mich an der stelle nicht wie meine Oberklasse verhalten, aber wie meine OberOberKlasse". Und das ist selten sinnvoll - wenn man das machen will, deutet es eher auf schlechte Architektur hin.
Ist die Relation isA nicht transitiv ?
Also
Aus X isA Y Und Y isA Z folgt X isA Z

mfg
Ern
 
Die Relation ja - aber das ist jetzt erst mal nur die Technik.

Ich kann durchaus in Java folgendes schreiben:

Code:
class Auto extends Pferd
Das ist nicht aber fachlich nicht sinnvoll. Der Zugriff auf eine Methode der Ober-Ober-Klasse unter Umgehung einer Methode der Oberklasse ist genauso fachlich nicht sinnvoll - und wird daher unterbunden.[/code]
 
Die Variable s hat den Datentyp Sohn und Datentyp Vater.
Ist das korrekt ?
Welche Variable `s`? In meinem gequoteten Text referenziere ich auf keine Variable `s`.
Generell ist es aber so, dass eine Variable ein Syntaxelement ist, was statisch ist und auch nur einen Compilezeit-Typ hat. Wenn du z.B. diesen Codeschnipsel hast:
Java:
Oberklasse s = new Unterklasse();
// mit Unterklasse extends Oberklasse
dann hat die Variable `s` den statischen Compilezeittyp `Oberklasse`.
Der Wert bzw. das Objekt, welches von dieser Variablen dann zur Laufzeit gehalten wird, hat in diesem konkreten Fall den (dynamischen) Laufzeittyp `Unterklasse`. Das muss aber nicht so sein. Man kann sich ja auch folgenden Code vorstellen:
Java:
Oberklasse s = gibMirEineInstanz();
und die Methode `gibMirEineInstanz()` könnte ja auch mal eine Instanz von nur Oberklasse zurückliefern.
 
Das muss aber nicht so sein. Man kann sich ja auch folgenden Code vorstellen:
Java:
Oberklasse s = gibMirEineInstanz();
und die Methode `gibMirEineInstanz()` könnte ja auch mal eine Instanz von nur Oberklasse zurückliefern.
I)
Ich verstehe dein Beispiel nicht.
Was meinst du damit?

II)
Habe mich auf mein Beispiel bezogen, bei dem s vorgekommen ist. Aber egal. Ich beziehe mich jetzt auf deine Beispiele.
Java:
Oberklasse s = new Unterklasse();
// mit Unterklasse extends Oberklasse
dann hat die Variable `s` den statischen Compilezeittyp `Oberklasse` und nicht den statischen Typ 'Unterklasse'
Ist das richtig ?

Eine Variable kann nur genau einen statischen Typ haben.
Ist das richtig?

Eine Variable kann nur genau einen dynamischen Typ (= dynamischen Laufzeittyp)statischen Typ haben.
Dieser hängt natürlich immer von der Stelle ab, wo sich das Programm gerade befindet.
Ist das richtig?

mfg
Ern
 
Eine Variable ist eine statische Sache. Eine Variable hat damit immer genau einen Typ.

Der Inhalt einer Variable ist aber dynamisch, denn er kann sich ja ständig ändern und daher hat der Inhalt keinen statischen typ sondenr kann dynamisch unterschiedliche Typen haben.

Aber jeder Inhalt hat natürlich wieder genau einen konkreten Typ. Also wenn Du mir new eine Instanz erzeugst, hat diese neue Instanz genau einen typ. (-> getClass gibt die Klasse zurück, getClass().getSimpleName() aufrufen und anzeigen lassen um das evtl. etwas in der Praxis auszuprobieren und zu verstehen.)

Code:
Oberklasse s = new Unterklasse();

Du erzeugst also eine neue Instanz, diese ist vom Typ "Unterklasse" und wird auch nie einen anderen Typ haben.
Du hast eine Variable s vom Typ Oberklasse. Die Variable wird auch immer diesen Typ haben. (Oder hat Java in den neuen Versionen da schon automatische Casts bekommen wie Kotlin? Dass nach eines Typ-Prüfung darauf auch direkt zugegriffen werden kann? Ist aber egal für das grobe Verständnis.)

Der Inhalt der Variable ist aber dynamisch. Denn ich kann da ja ganz viel Neues rein schreiben:

Code:
Oberklasse s = new Unterklasse(); // Danach ist eine Instanz vom Typ Unterklasse in der Variable.
// ...
s = new Oberklasse(); // Jetzt schreiben wir eine Instanz vom Typ Oberklasse in die Variable ...
// ...
 
I)
Ich verstehe dein Beispiel nicht.
Was meinst du damit?
Mit dem Beispiel `Oberklasse s = gibMirEineInstanz();` meine ich, dass man nicht weiss, welches konkrete Objekt insbesondere von welchem konkreten Laufzeittyp eine Methode zurückgibt. In Fall mit `Oberklasse s = new Unterklasse()` ist es offensichtlich, dass hier eine Instanz von `Unterklasse` erzeugt wird. Aber prinzipiell kann hier auch einfach eine Methode aufgerufen worden sein, die durch beliebige Faktoren/Bedingungen entscheidet, welche konkrete von Oberklasse abgeleitete Instanz zurückgeliefert wird.

Java:
Oberklasse s = new Unterklasse();
// mit Unterklasse extends Oberklasse
dann hat die Variable `s` den statischen Compilezeittyp `Oberklasse` und nicht den statischen Typ 'Unterklasse'
Ist das richtig ?
Korrekt.

Eine Variable kann nur genau einen statischen Typ haben.
Ist das richtig?
Wenn man es genau nehmen möchte, könnte man auch sagen, dass diese Variable ebenfalls den Typ `java.lang.Object` hat, weil ja jede Klasse auch von `java.lang.Object` erbt. Also eine Variable ist auch immer vom Typ jedes Obertyps des direkt an der Variablendeklaration genannten Typs.

Eine Variable kann nur genau einen dynamischen Typ (= dynamischen Laufzeittyp)statischen Typ haben.
Hier verstehe ich den Satzaufbau nicht. Was meinst du mit "einen dynamischen Typ (= dynamischen Laufzeittyp)statischen Typ"?
Eine Variable hat IMMER nur einen statischen Typ.
Das OBJEKT, welches ZUR LAUFZEIT in der Variablen ZU EINEM GEGEBENEN ZEITPUNKT gespeichert ist, hat einen dynamischen Laufzeittyp. Eine Variable (da es sich dabei um ein statisches Programm-/Syntaxkonstrukt handelt) hat niemals einen dynamischen bzw. Laufzeittyp.
 
Wenn man es genau nehmen möchte, könnte man auch sagen, dass diese Variable ebenfalls den Typ `java.lang.Object` hat, weil ja jede Klasse auch von `java.lang.Object` erbt. Also eine Variable ist auch immer vom Typ jedes Obertyps des direkt an der Variablendeklaration genannten Typs.

Also hier werden wir etwas unscharf.

Wenn ich eine konkrete Instanz habe, dann hat diese genau einen Typ. Dieser ist über getClass() klar abzufragen und das ist genau eine Klasse.

Diese eine Klasse hat aber eine Vererbung: Sie erbt von einer anderen Klasse (und wenn nicht, dann ist Object automatisch die Vaterklasse) und es werden ggf. Interfaces implementiert. Wenn ich die Klasse abgefragt habe (getClass() bei einer Instanz, wenn ich nur den Typ habe, dann kann ich Typ.class schreiben, um an die Klasse heran zu kommen), dann kann ich das z.B. abfragen mit getInterfaces() und getSuperclass().
 
Wenn man es genau nehmen möchte, könnte man auch sagen, dass diese Variable ebenfalls den Typ `java.lang.Object` hat, weil ja jede Klasse auch von `java.lang.Object` erbt. Also eine Variable ist auch immer vom Typ jedes Obertyps des direkt an der Variablendeklaration genannten Typs.
Da widersprichst du dir (siehe oben):
Denn dann müsste gelten:
dann hat die Variable `s` den statischen Compilezeittyp `Oberklasse` UND den statischen Typ 'Unterklasse'
(siehe oben)!!

Hier verstehe ich den Satzaufbau nicht. Was meinst du mit "einen dynamischen Typ (= dynamischen Laufzeittyp)statischen Typ"?
Du hast recht! Ich habe das Wort statisch vergessen zu löschen!

Deswegen nochmals:
Eine Variable kann nur genau einen dynamischen Typ (= dynamischen Laufzeittyp) Typ haben.
Dieser hängt natürlich immer von der Stelle ab, wo sich das Programm gerade befindet.
Ist das richtig?

mfg
Ern
 
Eine Variable kann nur genau einen dynamischen Typ (= dynamischen Laufzeittyp) Typ haben.
Dieser hängt natürlich immer von der Stelle ab, wo sich das Programm gerade befindet.

Eine Variable hat einen Inhalt von einem Typ. Dieser Typ muss "kompatibel" sein, d.h. bei primitiven Typen muss der Typ stimmen oder es umwandelbar sein. Bei Instanzen muss der Typ überein stimmen oder es eine Superclasse oder Instanz sein. Und der Typ ist dynamisch und hängt davon ab, was vorher zugewiesen wurde. Es ist unabhängig davon, wo du im Code bist. (Wobei Compiler da ggf. optimieren. Aber das ist ein anderes Thema.)

Nehmen wir einfach eine Methode, die abhängig von einer User-Eingabe eine neue Instanz einer Klasse erzeugt. Benutzer gibt ein: Apfel also wird ein Apfel erzeugt und zurück gegeben. Benutzer gibt ein: Auto - also wird ein Auto erzeugt und zurück gegeben. Zurück gegeben wird als ein Object (davon erbt alles direkt oder indirekt).

Eine Variable vom Typ Object kann das Ergebnis speichern, aber was ist denn da nun drin ist? Hängt halt davon ab, was rein getan wurde. Wo im Code du bist spielt keine Rolle. (Also hier z.B. nach der Zuweisung durch eine Methode wie oben beschrieben.)
 
Wo im Code du bist spielt keine Rolle. (Also hier z.B. nach der Zuweisung durch eine Methode wie oben beschrieben.)
Nein.
Es sei:
A Unterklasse B und B Unterklasse C

Hier ein Ausschnitt eines Programmas:
-----------
A a;
Bb;
Cc;
a = new A(); // (1)
a = new B(); // (2)
a = new C(); // (3)
-----------

a hat genau den statischen Typ A. Dieser wird beim Kompilieren festgelegt und ist unabhängig davon an welcher Stelle der Programmcode zur Laufzeit gerade abgearbeitet wird.

Der dynamische Laufzeittyp von a hängt natürlich davon ab, ob der Programmcode an der Stelle (1) , (2) oder (3) abgearbeitet wird.

mfg
Ern
 
Da widersprichst du dir (siehe oben):
Denn dann müsste gelten:
dann hat die Variable `s` den statischen Compilezeittyp `Oberklasse` UND den statischen Typ 'Unterklasse'
(siehe oben)!!
Ich wüsste nicht, wo sich das widerspricht. Ausser der Unschärfe, die @JustNobody richtigerweise angebracht hat, wäre es immer noch nachvollziehbar zu sagen, wenn du hast:
Java:
Oberklasse s = new Unterklasse();
dass der (statische) Typ der Variablen `s` gleich Oberklasse ist UND auch gleichzeitig jeder Obertyp von Oberklasse, also auch java.lang.Object. Das steht nicht im Widerspruch zu vorher Gesagtem.
ABER: Der statische Typ von `s` ist NICHT eine mögliche Subklasse davon. Also insbesondere ist der statische Typ von `s` eben NICHT Unterklasse (was du unterstellt hast und ich nicht behauptet hatte).

Der dynamische Laufzeittyp von a...
Nein. Ein statisches Syntaxelement, wie eben eine Variable, hat keinen dynamischen Laufzeittyp. Der Wert bzw. das Objekt, das in der Variablen gespeichert ist (oder von einem beliebigen statischen Ausdruck generiert wird) hat einen dynamischen Laufzeittyp.
Also:
- Statische Syntaxelemente wie eben eine Variable haben nur einen statischen Compilezeittyp.
- Objekte/Werte, die bei der Ausführung/Evaluierung von Ausdrücken (wie z.B. Variablen) generiert werden, haben dynamische Laufzeittypen.
 
Ich wüsste nicht, wo sich das widerspricht. Ausser der Unschärfe, die @JustNobody richtigerweise angebracht hat, wäre es immer noch nachvollziehbar zu sagen, wenn du hast:
Java:
Oberklasse s = new Unterklasse();
dass der (statische) Typ der Variablen `s` gleich Oberklasse ist UND auch gleichzeitig jeder Obertyp von Oberklasse, also auch java.lang.Object. Das steht nicht im Widerspruch zu vorher Gesagtem.
ABER: Der statische Typ von `s` ist NICHT eine mögliche Subklasse davon. Also insbesondere ist der statische Typ von `s` eben NICHT Unterklasse (was du unterstellt hast und ich nicht behauptet hatte).


Nein. Ein statisches Syntaxelement, wie eben eine Variable, hat keinen dynamischen Laufzeittyp. Der Wert bzw. das Objekt, das in der Variablen gespeichert ist (oder von einem beliebigen statischen Ausdruck generiert wird) hat einen dynamischen Laufzeittyp.
Also:
- Statische Syntaxelemente wie eben eine Variable haben nur einen statischen Compilezeittyp.
- Objekte/Werte, die bei der Ausführung/Evaluierung von Ausdrücken (wie z.B. Variablen) generiert werden, haben dynamische Laufzeittypen.

Ich wüsste nicht, wo sich das widerspricht.
1)
Du hast Recht!
Es sei:
A Unterklasse B und B Unterklasse C

Hier ein Ausschnitt eines Programmas:
-----------
A a;
B b;
C c;
c = new C();
-----------
Dann gilt:
Die Variable `c` hat den statischen Compilezeittyp `C und
Die Variable `c` hat den statischen Compilezeittyp `B` und
Die Variable `c` hat den statischen Compilezeittyp `A`.
Ist das richtig?

2)
Es sei:
A Unterklasse B und B Unterklasse C

Hier ein Ausschnitt eines Programmas:
-----------
A a;
B b;
C c;
a = new A(); // (1)
a = new B(); // (2)
a = new C(); // (3)
-----------

a hat genau den statischen Typ A und
a hat nicht den statischen Typ B und
a hat nicht den statischen Typ C.
Der Datentyp von a wird beim Kompilieren festgelegt und ist unabhängig davon an welcher Stelle der Programmcode zur Laufzeit gerade abgearbeitet wird.
Ist das richtig?

Der dynamische Laufzeittyp des Objektes auf das a zeigt hängt davon ab, ob der Programmcode an der Stelle (1) , (2) oder (3) abgearbeitet wird.
Ist das richtig?


mfg
Ern
 
Es hängt NICHT von der Stelle ab. Es hängt von der Zuweisung ab.

Das Beispiel mit der Methode, deren Rückgabe Du nicht kennen kannst, habe ich schon gebracht.

Somit ist die Stelle nicht aussagekräftig. In Deinem trivialen Beispiel ist diese zwar aussagekräftig, aber die Aussage das nach 1 eben eine Instanz von A und nach 2 eine instanz von B in der Variablen a zu finden ist, liegt einfach daran, dass Du eben exakt dies vorher zugewiesen hast. (Und das ist dann auch etwas, wo Compiler dann ggf. Optimierungen machen können und so ....) Daraus leitet sich aber eben nicht eine allgemeine Aussage ab.

a = someMethod();
Nur an der Stelle kannst Du eben NICHT erkennen, was da zur Laufzeit in der Variablen genau drin ist.

Das ist eben das, was @httpdiggest schon in #11 erwähnt und mit einem Link gepostet hat und was Du Dir evtl. noch einmal ansehen solltest.

Aber ja: Der Datentyp einer Variable wird vom Programm festgelegt aber die Unabhängigkeit von der Stelle verstehe ich nicht. Jede Variable hat einen Gültigkeitsbereich. Somit ist die Stelle auch durchaus wichtig.
 
Ich verstehe leider deine Antwort nicht.
Kannst du bitte konkret meine Fragen meines Postings beantworten und falls meine Behauptungen falsch sind dazu jeweils ein Gegenbeispiel bringen ?

mfg
Ern
 
Diskutiert ihr gerade, was der statische und dynamische Typ einer Variablen ist?

Statischer Typ: Ist der Typ einer Variablen, die bei der Variablendeklaration angegeben sind. Dieser Typ ist bei der Kompilierung schon bekannt.

Dynamischer Typ: Ist der Typ einer Variable, welcher erst zur Laufzeit bekannt ist und kann vom statischen Typen abweichen.

Der dynamische Laufzeittyp des Objektes auf das a zeigt hängt davon ab, ob der Programmcode an der Stelle (1) , (2) oder (3) abgearbeitet wird.
Ist das richtig?
Im Prinzip hast du damit Recht, wenn du damit meinst, dass der dynamische Typ davon abhängt, was dieser Variablen zugwiesen wird. Das muss ja nicht immer so eindeutig sein, wie in deinem Beispiel. Der "Inhalt" kann ja auch aus einer anderen Quelle kommen.

Auto auto = autohaus.getCheapestModel(); // da kann dann allerlei dabei rauskommen

Falls ich euer Problem mißverstanden habe, dann bitte ich um Verzeihung und ziehe mich wieder still zurück 😉
 
1)
Du hast Recht!
Es sei:
A Unterklasse B und B Unterklasse C

Hier ein Ausschnitt eines Programmas:
-----------
A a;
B b;
C c;
c = new C();
-----------
Dann gilt:
Die Variable `c` hat den statischen Compilezeittyp `C und
Die Variable `c` hat den statischen Compilezeittyp `B` und
Die Variable `c` hat den statischen Compilezeittyp `A`.
Ist das richtig?

Also das ist nicht richtig. Wenn Du meintest:

Die Variable `c` hat den statischen Compilezeittyp `C und
Die Variable `b` hat den statischen Compilezeittyp `B` und
Die Variable `a` hat den statischen Compilezeittyp `A`.

dann wäre es richtig.

2)
Es sei:
A Unterklasse B und B Unterklasse C

Hier ein Ausschnitt eines Programmas:
-----------
A a;
B b;
C c;
a = new A(); // (1)
a = new B(); // (2)
a = new C(); // (3)
-----------

a hat genau den statischen Typ A und
a hat nicht den statischen Typ B und
a hat nicht den statischen Typ C.
Der Datentyp von a wird beim Kompilieren festgelegt und ist unabhängig davon an welcher Stelle der Programmcode zur Laufzeit gerade abgearbeitet wird.
Ist das richtig?

Der dynamische Laufzeittyp des Objektes auf das a zeigt hängt davon ab, ob der Programmcode an der Stelle (1) , (2) oder (3) abgearbeitet wird.
Ist das richtig?

Ja, das ist richtig, denn bei 1, 2 und 3 werden Zuweisungen gemacht. Der Programmcode, der einer Variable einen neuen Wert zuweist, kann der Variablen eine Referenz auf einen anderen Typen zuweisen.

Ich selbst würde aber nicht von "dynamischem Typ der Variable" sprechen, denn es geht hier ja nicht um den Typ der Variable (Der ist statisch!) sondern um den Typ der gespeicherten Referenz.
 
1)
Nochmals zu dem Programm
A Unterklasse B und B Unterklasse C
-----------
A a;
B b;
C c;
c = new C();
-----------
Dann gilt:
Die Variable `c` hat den statischen Compilezeittyp `C und
Die Variable `c` hat den statischen Compilezeittyp `B` und
Die Variable `c` hat den statischen Compilezeittyp `A`.

In dem Buch: "Java als erste Programmierprache" (Heinisch, Müller-Hofmann, Goll) steht (S. 365 der 5.Auflage)
"Ein Objekt der Klasse Sohn ist stets vom Typ Sohn und vom Typ Vater."
(wobei Sohn Unterklasse von Vater ist).
Damit wäre meine Behauptung richtig, oder was meinst du dazu?

2)
Du sagst:
"Ich selbst würde aber nicht von "dynamischem Typ der Variable" sprechen,
denn es geht hier ja nicht um den Typ der Variable
(Der ist statisch!) sondern um den Typ der gespeicherten Referenz."

Der dynamische Laufzeittyp des Objektes auf das a zeigt ....
Das ist doch aber korrekt ausgedrückt, oder was meinst du dazu?

mfg
Ern
 
In dem Buch: "Java als erste Programmierprache" (Heinisch, Müller-Hofmann, Goll) steht (S. 365 der 5.Auflage)
"Ein Objekt der Klasse Sohn ist stets vom Typ Sohn und vom Typ Vater."
(wobei Sohn Unterklasse von Vater ist).
Damit wäre meine Behauptung richtig, oder was meinst du dazu?

Nein, deine Behauptung ist falsch! Du redest von einer Variablen. Das Buch von Objekten, also Instanzen.

Variablen sind Behälter, in denen Du Objekte speichern kannst.

Ein Hund ist ein Säugetier ist ein Lebewesen ist ein Objekt.
Ein Elefant ist auch ein Lebewesen und ein Objekt.

Eine Hundebox nimmt nur Hunde auf. Schäferhund ist ein Hund -> kann also in die Hundebox.

Wenn die Box aber auch vom Typ Säugetier, Lebewesen und Objekt wäre, dann würde der Elephant ja auch da rein passen? Das ist aber nicht so, denn wenn da ein Hund drin ist, will ich ja das Verhalten des Hundes haben. Ich will also bellen() aufrufen können. Das geht aber bei Elefant nicht ...

2)
Du sagst:
"Ich selbst würde aber nicht von "dynamischem Typ der Variable" sprechen,
denn es geht hier ja nicht um den Typ der Variable
(Der ist statisch!) sondern um den Typ der gespeicherten Referenz."

Der dynamische Laufzeittyp des Objektes auf das a zeigt ....
Das ist doch aber korrekt ausgedrückt, oder was meinst du dazu?

Also das sind jetzt sprachliche Feinheiten. Das Problem ist, dass allgemein gerne bei Vererbung von "ist ein" gesprochen wird. Daher ist ein Schäferhund ein Hund, ein Säugetier, ein Lebewesen und ein Objekt.

Und eine Variable, in der ich einen Hund speichern kann, kann alles aufnehmen: Schäferhund, Dackel, Dobermann, ...

Aber wichtig ist: Für eine Instanz ist die Klasse festgelegt. Ein Schäferhund ist und bleibt ein Schäferhund. Der verwandelt sich nicht in einen Dackel oder so...

Dynamisch ist also nur der Inhalt (und mit dem Inhalt kann sich der Typ ändern).

Ich gehe aber davon aus, dass Du das Richtige meinst.
 
Nein, deine Behauptung ist falsch! Du redest von einer Variablen. Das Buch von Objekten, also Instanzen.
...
Ich gehe aber davon aus, dass Du das Richtige meinst.
Ich versuche nochmals eure Erklärungen in meine Worte zu fassen.
Es sei:
A Unterklasse B und B Unterklasse C

Hier ein Ausschnitt eines Programmas:
-----------
A a;
B b;
C c;
a = new A(); // (1)
a = new B(); // (2)
a = new C(); // (3)
-----------

1)
Jede Variable hat genau einen statischen Typ.
Also:
Die Variable a hat genau den statischen Typ A und deshalb nicht den statischen Typ B und
nicht den statischen Typ C.
Der Datentyp von a wird beim Kompilieren festgelegt und ist unabhängig davon an welcher Stelle der Programmcode zur Laufzeit gerade abgearbeitet wird.
Frage1:
Ist das richtig?

2)
Der dynamische Laufzeittyp des Objektes auf das a zeigt kann sich während der Laufzeit ändern (je nachdem, was a zugewiesen wurde), wobei das zugewiesene Objekt eine Unterklasse von A sein muss.
Frage2:
Ist das richtig?

mfg
Ern
 

Neue Themen


Zurück
Oben