Polymorphie Programmanalyse: Was wird zurückgegeben?

lenaa25

Mitglied
Ich habe eine Frage zu der Ausgabe des folgenden Programms:

public class A {
int x = 1;
static double y = 3.0;

public A() {
x = (int) y;
}

public A(int x) {
this.x = this.getX() + x;
}

int getX() {
return this.x;
}
public void f(float z) {
y *= z;

}
}

public class B extends A {
int x = 7;

public B(int x) {
super(x);
this.x = x;
}

public B() {
this.y += 1.0;
}

int getX() {
return this.x;
}

public void f(long z) {
y += z;
}

public void f(double z) {
y -= z;
}
}

Wenn ich in der main-Methode
A ab = new B(5);
System.out.println(ab.x + " " + A.y);

ausgebe, wird 5 und 3.0 zurückgegeben. 3.0 kann ich nachvollziehen, an der statischen Variable y ändert sich ja nichts.

5 wiederum verstehe ich nicht:

Als erstes wird der Konstruktor mit dem übergebenen Parameter 5 (int x) der Oberklasse A durch das implizite super() im Konstruktor mit (int x) der Klasse B aufgerufen.

In diesem wird this.x (hier:1) auf this.getX() + x gesetzt. Ich habe herausgefunden, dass um den Wert von this.getX() zu bekommen, die getX() Methode der Klasse B ausgeführt wird. Warum nicht die der Klasse A?

In der getX() Methode der Klasse B steht die Anweisung return this.x;

Damit nachher im Konstruktor der Klasse A this.x=this.getX() + x dann 5 ergibt (Ausgabe von ab.x), muss this.x in der getX() Methode der Klasse B 0 gewesen sein.. Wieso 0? Ich dachte 7?

Ich hoffe ich habe meine Unklarheiten einigermaßen verständlich erklärt und mir kann jemand helfen.
Danke!
 

Robert Zenz

Top Contributor
Als erstes wird der Konstruktor mit dem übergebenen Parameter 5 (int x) der Oberklasse A durch das implizite super() im Konstruktor mit (int x) der Klasse B aufgerufen.
Du uebersiehst dass du x versteckst/ueberschattest in B. Methoden werden von einer ableitenden Klasse *ueberschrieben*, Variablen aber nicht. Also wenn man deine Klasse betrachtet, sieht die so aus:

Code:
class B {
    int A.x;
    int B.x;
    
    B.getX();
    B.f(long);
    A.f(float);
    B.f(double);
}

Und dementsprechend finden auch Operationen auf dieser Instanz statt. Wenn jemand getX aufruft, bekommt er immer die Methode welche definiert ist in B. Das wird auch klarer wenn du die Annotationen verwendest:

Java:
public class B extends A {
    int x = 7;
    
    public B(int x) {
        super(x);
        this.x = x;
    }
    
    public B() {
        A.y += 1.0;
    }
    
    @Override
    int getX() {
        return this.x;
    }
    
    public void f(long z) {
        y += z;
    }
    
    public void f(double z) {
        y -= z;
    }
}

Allerdings operieren alle Methoden welche noch zu A gehoeren auf dem Zustand von A.

So...was deine eigentliche Frage angeht, wird es komplizierter. Du rufst im Konstruktor von A die Methode B.getX() auf, aber der Konstruktor von B ist noch am laufen. Also der Ablauf sieht dann so:

Code:
B(5)
  A(5)
    B.getX() liefert B.x --> "7" weil noch nicht gesetzt
    A.x = 7 + 5 --> 13
  B.x = 5

System.out.println(ab.x) --> B.x --> 5

Wenn du ein x zwischen A und B teilen willst, machst du es in A protected und entfernst die Deklaration in B. Dann wird immer A.x verwendet bei jedem Aufruf.
 

lenaa25

Mitglied
Vielen vielen Dank für die so ausführliche Antwort, das hat mir sehr weiter geholfen! Eine letzte Frage hätte ich noch, mich verwirrt es immer wenn ich "this.x" oder "this.y" bei Klassen mit Vererbungsbeziehungen sehe. Wenn z.B. in einer der Methoden von der Klasse B this.y steht, ist die Variable von Klasse A gemeint (ich glaube, weil in Klasse B keine Variable y existiert?). Angenommen es gäbe eine neue Variable int y; in Klasse B, würde man sich dann mit dem Aufruf von this.y auf diese Variable in Klasse B beziehen? Mir ist es nie ganz klar was mit this.variable gemeint ist bzw. was der Unterschied zu this() ist..
 

Robert Zenz

Top Contributor
Angenommen es gäbe eine neue Variable int y; in Klasse B, würde man sich dann mit dem Aufruf von this.y auf diese Variable in Klasse B beziehen
Ja. Variablen gelten immer im aktuellsten Kontext. Also mal ein extremes Beispiel:

Java:
public class A {
    private int value = 1;
 
    public void action() {
        System.out.println(value); // A.value == 1
    }
}

public class B extends A {
    int value = 2;
 
    @Override
    public void action() {
        super.action(); // Prints "1"
    
        System.out.println(value); // B.value == 2
    }
 
    public void anotherAction(int value) {
        System.out.println(value); // Whatever is given.
        System.out.println(this.value); // B.value == 2
    }
 
    public void yetAnotherAction() {
        int value = 3;
        System.out.println(value); // B.yetAnotherAction.value == 3
        System.out.println(this.value); // B.value == 2
    }
}

Angenommen es gäbe eine neue Variable int y; in Klasse B, würde man sich dann mit dem Aufruf von this.y auf diese Variable in Klasse B beziehen?
Ja. Weil der Kontext von Klasse B gewinnt (weil aktuellste).

Mir ist es nie ganz klar was mit this.variable gemeint ist bzw. was der Unterschied zu this() ist..
this gibt einfach nur die aktuelle Instanz als Kontext/Scope an. So wie im Beispiel oben wird es eigentlich nur verwendet wenn mann eine Variable hat welche ident mit einer Variable in der Klasse heiszt. Inbesondere siehst du das bei Konstruktoren oder Settern.

Java:
public class Holder {
    private int value = 0;
 
    public Holder(int value) {
        super();
    
        this.value = value;
    }
 
    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

Was ebenfalls vermutlich etwas zu deiner Verwirrung beitragen wird, ist dass deine Variablen in den Klassen im Moment immer package-private sind. Es gibt verschiedene Sichtbarkeiten in Java, und meiner Meinung nach ist package-private, also kein Modifier, die Furchtbarste von allen. Eine ableitende Klasse kann dann nicht darauf zugreifen, aber alles im selben Paket, was sehr schnell zu extrem schlechten Abhaengigkeiten fuehrt. Wenn du die Sichtbarkeit der Felder korrekt setzt, wirkt es auch gleich um einiges verstaendlicher auf Anhieb:

Java:
public class A {
    private int oneValue = 0;
    private int anotherValue = 10;
  
    public void action() {
        System.out.println(oneValue); // == 0
        System.out.println(anotherValue); // == 10
    }
}

public class B extends A {
    private int oneValue = 1;
  
    @Override
    public void action() {
        System.out.println(oneValue); // == 1
        System.out.println(anotherValue); // Kompilierfehler, "anotherValue" nicht zugreifbar.
    }
}

Und wenn du Variablen teilen willst mit ableitenden Klassen, machst du diese protected:

Java:
public class A {
    private int oneValue = 0;
    protected int anotherValue = 10;
 
    public void action() {
        System.out.println(oneValue); // == 0
        System.out.println(anotherValue); // == 10
    }
}

public class B extends A {
    private int oneValue = 1;
 
    @Override
    public void action() {
        System.out.println(oneValue); // == 1
        System.out.println(anotherValue); // == 10
    }
}
 

lenaa25

Mitglied
Ja. Variablen gelten immer im aktuellsten Kontext. Also mal ein extremes Beispiel:

Java:
public class A {
    private int value = 1;
 
    public void action() {
        System.out.println(value); // A.value == 1
    }
}

public class B extends A {
    int value = 2;
 
    @Override
    public void action() {
        super.action(); // Prints "1"
   
        System.out.println(value); // B.value == 2
    }
 
    public void anotherAction(int value) {
        System.out.println(value); // Whatever is given.
        System.out.println(this.value); // B.value == 2
    }
 
    public void yetAnotherAction() {
        int value = 3;
        System.out.println(value); // B.yetAnotherAction.value == 3
        System.out.println(this.value); // B.value == 2
    }
}


Ja. Weil der Kontext von Klasse B gewinnt (weil aktuellste).


this gibt einfach nur die aktuelle Instanz als Kontext/Scope an. So wie im Beispiel oben wird es eigentlich nur verwendet wenn mann eine Variable hat welche ident mit einer Variable in der Klasse heiszt. Inbesondere siehst du das bei Konstruktoren oder Settern.

Java:
public class Holder {
    private int value = 0;
 
    public Holder(int value) {
        super();
   
        this.value = value;
    }
 
    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

Was ebenfalls vermutlich etwas zu deiner Verwirrung beitragen wird, ist dass deine Variablen in den Klassen im Moment immer package-private sind. Es gibt verschiedene Sichtbarkeiten in Java, und meiner Meinung nach ist package-private, also kein Modifier, die Furchtbarste von allen. Eine ableitende Klasse kann dann nicht darauf zugreifen, aber alles im selben Paket, was sehr schnell zu extrem schlechten Abhaengigkeiten fuehrt. Wenn du die Sichtbarkeit der Felder korrekt setzt, wirkt es auch gleich um einiges verstaendlicher auf Anhieb:

Java:
public class A {
    private int oneValue = 0;
    private int anotherValue = 10;
 
    public void action() {
        System.out.println(oneValue); // == 0
        System.out.println(anotherValue); // == 10
    }
}

public class B extends A {
    private int oneValue = 1;
 
    @Override
    public void action() {
        System.out.println(oneValue); // == 1
        System.out.println(anotherValue); // Kompilierfehler, "anotherValue" nicht zugreifbar.
    }
}

Und wenn du Variablen teilen willst mit ableitenden Klassen, machst du diese protected:

Java:
public class A {
    private int oneValue = 0;
    protected int anotherValue = 10;
 
    public void action() {
        System.out.println(oneValue); // == 0
        System.out.println(anotherValue); // == 10
    }
}

public class B extends A {
    private int oneValue = 1;
 
    @Override
    public void action() {
        System.out.println(oneValue); // == 1
        System.out.println(anotherValue); // == 10
    }
}
Achsoo, jetzt ergibt das alles schon viel mehr Sinn! Sehr gut erklärt, dankeschön! :)
 

Neue Themen


Oben