Zuweisungskompatibilität zwischen Vererbungsbeziehungen (Polymorphie)

NeoLexx

Mitglied
Hallo Leute, ihr könnt mir bestimmt weiterhelfen.

Hier seht ihr die Oberklasse zu der gleich folgenden Unterklasse von ElementGalaxie:
1602799599083.png
Unterklasse:
1602799715616.png
In der Klasse habe ich eine setTypInt Methode deklariert, welche den typint setzen soll, diese möchte ich später aufrufen und leider gibt mir Eclipse eine Fehlermeldung aus.
1602800561235.png

Nun bilde ich in einer anderen Klasse eine Referenz auf die eben dargestellten Klassen:

1602799893265.png
Wichtig ist hier die Zeile 21.
1602799932408.png
Hier gebe ich an wie viele Felder mein Array haben soll, in Zeile 40.
1602800075086.png
Und nun kommen wir endlich zu dem was ich nicht Verstehe, zu sehen in Zeile 99. Dort versuche ich eine Methode der Klasse SonnenSystem im allgemeinen und im speziellen, von der Instanz der Klasse SonnenSystem, mit dem Namen "elmenteGalaxie [newx] [newy]" aufzurufen. Jedoch gibt mir Eclipse eine Fehlermeldung aus.

Ich hätte jetzt gedacht, dass wegen Zuweisungskompatibilität zwischen Oberklassen und Unterklassen, dies möglich sein sollte.

Schon etwas spät gerade, hoffe ich habe mich halbwegs verständlich ausgedrückt. Und danke schon mal fürs einlesen.

(Übrigens ist mir durchaus klar, dass ich den Typ des Sonnensystems über weiter Klassen darstellen kann, ich wollte es hier zu Testzwecken jedoch über einen primitiven Datantyp machen.)
 
Zum Zeitpunkt der Übersetzung ist dem Compiler der statische Typ - hier also ElementGalaxie-Array - bekannt. elementGalaxie[x][y] liefert aus Sicht des Compilers also ein Element vom Typ ElementGalaxie. Jetzt die Frage: verfügt ElementGalaxy über die setTyp-Methode?
 
Zum Zeitpunkt der Übersetzung ist dem Compiler der statische Typ - hier also ElementGalaxie-Array - bekannt. elementGalaxie[x][y] liefert aus Sicht des Compilers also ein Element vom Typ ElementGalaxie. Jetzt die Frage: verfügt ElementGalaxy über die setTyp-Methode?
Nein, die besagte Methode ist nur in der Unterklasse deklariert, denn ein ElementGalaxie braucht gar kein Typ. Der Compiler will die Methode in der Oberklasse sehen, das habe ich ja auch schon verstanden. Du sprichst von "zum Zeitpunkt der Übersetzung" heißt, dass das es eine andere Interpretation des Compiler zur Laufzeit gibt?

Wieso Screenshots?
Ja, sorry. Irgendwie fand ich es mit Screenshots übersichtlicher. Werde aber nächstes mal auf die Code-Funktion des Forums zurückgreifen.
 
Zur Laufzeit spielt der Compiler keine Rolle mehr 🙂

Der Compiler macht aus Quelltext Bytecode und dieser Bytecode wird von der JVM (Java Virtual Machine) ausgeführt.
Ja, dieser Umstand ist mir bekannt. Ich verstehe nur nicht wie mir das nun weiter helfen soll.
Du sagst es gibt also ein Unterschied zwischen dem was mir der Eclipse interpreter als Fehler anzeigt und dem was mir der Compiler als Fehler ausspuckt?

Werde jetzt erstmal versuchen zu schlafen. Morgen nachmittags geht es weiter. Übrigens, auch wenn es nicht direkt so rüber kommt, ich bin auf jeden Fall dankbar für eure Zeit. 😉
 
Ja, dieser Umstand ist mir bekannt. Ich verstehe nur nicht wie mir das nun weiter helfen soll.
Eclipse zeigt Dir an, dass der Compiler meckert. Warum steht hier:
Zum Zeitpunkt der Übersetzung ist dem Compiler der statische Typ - hier also ElementGalaxie-Array - bekannt. elementGalaxie[x][y] liefert aus Sicht des Compilers also ein Element vom Typ ElementGalaxie. Jetzt die Frage: verfügt ElementGalaxy über die setTyp-Methode?

Wenn du ein Objekt vom statischen Typ ElementGalaxie hast, kannst Du - ohne weitere Maßnahmen - nur auf die Methoden dieses Typs zugreifen.

Du könntest casten, um dem Compiler explizit zu sagen: hey, der Laufzeittyp von elementGalaxie[x][y] ist gar nicht ElementGalaxie sondern SonnenSystem. Das würde dann so ausssehen: ((SonnenSystem) elementGalaxie[x][y]).setTyp(random.nextInt(5));

Wenn elementGalaxie[x][y] zur Laufzeit aber tatsächlich ein Objekt anderen Typs liefert, der kein Subtyp von SonnenSystem ist, dann würde die Zeile zur Laufzeit eine ClassCastException auslösen.
 
Eclipse zeigt Dir an, dass der Compiler meckert. Warum steht hier:


Wenn du ein Objekt vom statischen Typ ElementGalaxie hast, kannst Du - ohne weitere Maßnahmen - nur auf die Methoden dieses Typs zugreifen.

Du könntest casten, um dem Compiler explizit zu sagen: hey, der Laufzeittyp von elementGalaxie[x][y] ist gar nicht ElementGalaxie sondern SonnenSystem. Das würde dann so ausssehen: ((SonnenSystem) elementGalaxie[x][y]).setTyp(random.nextInt(5));

Wenn elementGalaxie[x][y] zur Laufzeit aber tatsächlich ein Objekt anderen Typs liefert, der kein Subtyp von SonnenSystem ist, dann würde die Zeile zur Laufzeit eine ClassCastException auslösen.
Ja, das hat funktioniert.

Jedoch gehen durch so eine Vorgehensweise, die Vorteile die sich aus der Zuweisungskompatibilität ergeben, verloren, jedenfalls so wie ich das bisher verstanden habe. Blicke da gerade noch nicht ganz durch.

Ich bilde doch kurz vorher für alle Variablen elementGalaxie [newx] [newy] eine Referenz auf die Instanzen der Klasse SonnenSystem ab. Müsste der Compiler und später die Runtime nicht wissen müssen, dass ein Referenz auf den DatenTyp SonnenSystem besteht und ich damit auch Methoden dieser Unterklasse aufrufen kann? Wo liegt da der Denkfehler?

Java:
        public void erzeugeSpiralArm(int laufrichtung, int anzahlSonneEingabe, int entfernungSonne) { //erzeugung eines Spiralarms
        ...
                            if (newx<groese&&newy<groese&&newx>0&&newy>0) {
                            elementGalaxie [newx] [newy] = new SonnenSystem();
                            elementGalaxie [newx] [newy].setKoordinaten(newx, newy);
                            elementGalaxie [newx] [newy].setTyp (random.nextInt(5));
        ...
 
Ich bilde doch kurz vorher für alle Variablen elementGalaxie [newx] [newy] eine Referenz auf die Instanzen der Klasse SonnenSystem ab. Müsste der Compiler und später die Runtime nicht wissen müssen, dass ein Referenz auf den DatenTyp SonnenSystem besteht und ich damit auch Methoden dieser Unterklasse aufrufen kann? Wo liegt da der Denkfehler?

Java:
        public void erzeugeSpiralArm(int laufrichtung, int anzahlSonneEingabe, int entfernungSonne) { //erzeugung eines Spiralarms
        ...
                            if (newx<groese&&newy<groese&&newx>0&&newy>0) {
                            elementGalaxie [newx] [newy] = new SonnenSystem();
                            elementGalaxie [newx] [newy].setKoordinaten(newx, newy);
                            elementGalaxie [newx] [newy].setTyp (random.nextInt(5));
        ...
Den Referenz Typ gibst du bei der Deklaration an mit ElementGalaxie eg = new Sonnensystem(); erzeugst du ein ElementGalaxie Refferenz die auf ein Sonnensystem Objekt zeigt.
 
Du erstellst ja ein Array vom Typ ElementGalaxie, damit weiß der Compiler halt nur von ElementGalaxie und nicht von allen Objekten die davon erben.
Java:
new ElementGalaxie[size][size]()

Du könntest z.b. sowas machen, dann kannst Du normal auf die Methoden zugreifen.
Java:
SonnenSystem sonnenSystem = new SonnenSystem();
sonnenSystem.setKoordinaten(newx, newy);
sonnenSystem.setTyp (random.nextInt(5));
elementGalaxie [newx] [newy] = sonnenSystem;

Das ändert aber nichts daran, dass Du es casten musst, wenn Du es wieder aus dem Array herausholst.
Java:
if(elementGalaxie[x][y] instanceof SonnenSystem) {
    SonnenSystem sonnenSystem = (SonnenSystem)elementGalaxie [x][y];
    // arbeite mit sonnenSystem
}
 
Den Referenz Typ gibst du bei der Deklaration an mit ElementGalaxie eg = new Sonnensystem(); erzeugst du ein ElementGalaxie Refferenz die auf ein Sonnensystem Objekt zeigt.
Aber genau das meine ich, wieso sollte ich bei der Code-Implementierung ein ElementGalaxie Refferenz erzeugen und damit dann auf ein SonnenSystem Objekt zeigen, wenn nicht dafür, um dann auf die Methoden von SonnenSystem zugriff zu haben?

Du erstellst ja ein Array vom Typ ElementGalaxie, damit weiß der Compiler halt nur von ElementGalaxie und nicht von allen Objekten die davon erben.
Java:
new ElementGalaxie[size][size]()

Du könntest z.b. sowas machen, dann kannst Du normal auf die Methoden zugreifen.
Java:
SonnenSystem sonnenSystem = new SonnenSystem();
sonnenSystem.setKoordinaten(newx, newy);
sonnenSystem.setTyp (random.nextInt(5));
elementGalaxie [newx] [newy] = sonnenSystem;

Das ändert aber nichts daran, dass Du es casten musst, wenn Du es wieder aus dem Array herausholst.
Java:
if(elementGalaxie[x][y] instanceof SonnenSystem) {
    SonnenSystem sonnenSystem = (SonnenSystem)elementGalaxie [x][y];
    // arbeite mit sonnenSystem
}
Das sind ja auch Lösungen wie sie mihe7 vorgeschlagen hat. Da kann ich nur nochmal auf das verweisen was ich nach seinen Poste gefragt hatte.

Ja, das hat funktioniert.

Jedoch gehen durch so eine Vorgehensweise, die Vorteile die sich aus der Zuweisungskompatibilität ergeben, verloren, jedenfalls so wie ich das bisher verstanden habe. Blicke da gerade noch nicht ganz durch.
 
Aber genau das meine ich, wieso sollte ich bei der Code-Implementierung ein ElementGalaxie Refferenz erzeugen und damit dann auf ein SonnenSystem Objekt zeigen, wenn nicht dafür, um dann auf die Methoden von SonnenSystem zugriff zu haben?
Wenn du nur SonnenSystem Instanzen speichern willst, dann macht es keinen Sinn.

Aber so eine Hierarchie hat man ja nur, wenn es mehrere Unterklassen von ElementGalaxie hat. Und wenn du diese ebenso in dem Array speichen können willst, dann geht das nur, wenn das Array von Typ ElementGalaxie ist.
 
Aber genau das meine ich, wieso sollte ich bei der Code-Implementierung ein ElementGalaxie Refferenz erzeugen und damit dann auf ein SonnenSystem Objekt zeigen, wenn nicht dafür, um dann auf die Methoden von SonnenSystem zugriff zu haben?
Zu dem von @JustNobody mal ergänzend:

Angenommen du hast ein Interface Shape, das hat eine Methode int berechneUmfang() und int berechneFlaeche().
Dann hast du mehrere Klasse, die dieses Interface implementieren:
* Rechteck
* Kreis
* Quadrat
..

Dann kannst ein Array von Shape anlegen und darin all diese Objekte speichern. Und du kannst auf jedem Objekt die Methode berechneUmfang und berechneFlaeche aufrufen - weil ja Shape definiert das es diese Methoden gibt. Wie die implementiert wird, macht jede Klasse anders.

Sprich, die Intention sollte sein, wenn du ElementGalaxie anlegst, dass dich dann im weiteren Verlauf nicht interessiert ob da ein Sonnensystem oder ein SchwarzesLoch oder ähnliches drin ist. Sondern der restliche Code damit klarkommt, dass er ein ElementGalaxie Objekt hat und da Methoden hat, um mit dem zu interagieren (die jeweilige Implementierung ist dann in den abgeleiteten Klassen anders)
 
Aber genau das meine ich, wieso sollte ich bei der Code-Implementierung ein ElementGalaxie Refferenz erzeugen und damit dann auf ein SonnenSystem Objekt zeigen, wenn nicht dafür, um dann auf die Methoden von SonnenSystem zugriff zu haben?

Du hast durchaus Zugriff auf die Methoden von SonnenSystem, standardmäßig jedoch begrenzt auf diejenigen, die in ElementGalaxie existieren.

Mal ein Beispiel:

Java:
public void center(ElementGalaxie galaxie) {
    galaxie.setKoordinaten(0, 0);
}

Woher soll der Compiler wissen, ob diese Methode vielleicht irgendwann einmal mit einem SonnenSystem-Objekt aufgerufen wird? Richtig, gar nicht und deswegen können auch nur die Methoden aufgerufen werden, die in ElementGalaxie definiert wurden.

Aber: das bedeutet nicht, dass der Code in SonnenSystem keine Rolle spielt. Wird nämlich in SonnenSystem eine Methode von ElementGalaxie, z. B. setKoordinaten, überschrieben, dann würde für ein betreffendes SonnenSystem-Objekt automatisch die Methode von SonnenSystem ausgeführt (Polymorphie).
 
[CODE lang="java" title="Beispiel(e) zu Polymorphie" highlight="6-7"]public class Main2 {
public static void main(String[] args) {
WasGrosses g1 = new WasGrosses(); // geht
WasGrosses g2 = new WasNochGroesseres(); // geht
g1.a(); // geht
// g1.b(); // compile error
((WasNochGroesseres) g1).b(); // runtime error
g2.a(); // geht
((WasNochGroesseres) g2).b(); // geht
}
}

class WasGrosses {
public void a() {
System.out.println("a");
}
}

class WasNochGroesseres extends WasGrosses {
public void b() {
System.out.println("b");
}
}[/CODE]
 
Großen Dank an euch für eure Mühe und eure Zeit, ihr habt mich mit euren Ausführung jedes mal ein Stück weitergebracht. 😉
Zunächst wollte ich auf eure Kommentare im Einzelnen eingehen, aber ich glaube es so zu machen macht mehr Sinn.

Ich weiß nun wo das Problem, in meiner Vorstellung von der ganzen Sache, liegt. Ich versuche es mal zu erläutern:

Allein schon die Methode, die den Fehler verursacht, stellt das Problem dar. Ich möchte dort den Typ der Sonne festlegen,
daran lassen sich bereits zwei Dinge kritisieren:

-Erstens eignet sich diese Methode nicht für das Konzept von Polymorphie, denn diese muss sich in jeder Unterklasse und auch der Oberklasse wiederfinden. Das tut sie aber nicht, da eine Methode und die dazugehörigen Attribute, TypSonne, z.B. in der Unterklasse LeerRaum gar kein Sinn machen.
-Das zweite ergibt sich aus der Frage, wie soll sich dann der Typ der Sonne festlegen lassen? Natürlich über weitere Unterklassen von SonnenSystem.

Dadurch verschwindet das ursprüngliche Problem, der Zuweisungen eines Typs, direkt. Ich muss dann auch keine bedingte Anweisung erzeugen, die vorher Abfragt ob das Objekt nun eine SonnenSystem ist oder nicht.

Wenn ich mir das Ganze weiter überlege, dann fällt mir keine einzige Methode ein, die ich ausschließlich in der Unterklasse SonnenSystem implementieret habe und die ich gleichzeitig von außerhalb aufrufen möchte.

Mir fällt jedoch ein gutes Beispiel für eine Methode ein, welche das Konzept der Polymorphie gut repräsentiert. Die Methode erzeugeSystem(), sollte bereits in der Oberklasse implementiert werden. Natürlich wird so eine System anders erzeugt wenn es sich um ein Leeren Raum handelt, oder einen Nebel oder halt ein Schwarzes Loch. Dadurch muss die Methode in jeder Unterklasse überschrieben werden.
 
Java:
package x;

public abstract class Animal
{

 public Animal(String name,int age)
{

 this.name =name;
 this.age=age;
}
private String name="";
private int age;
protected abstract void move();
protected abstract void eat();

public String getName(){return this.name;}

public int getAge(){return this.age}
}
class Bird extends Animal{
@Override
public void move(){
System.out.println("This animals fly");
}
@Override
public void eat(){
System.out.print("...");
}
}

// place for more types of animals like a panda

class Zoo{

List<Animal>animals =new ArrayList();
public static final void main(String...args)
{
animals.add(new Bird("Lotus",2));
animals.add(new Panda("Rhyan",2));
animals.add(new Kangoru("Peter",5));
.
.
.
for( Animals animal: animals)
{
// output
}
}}
Ich habe jetzt einfach mal ein Beispiel aus dem Ärmel gezaubert, um dieses Thema vll ganz grob zu veranschaulichen und um vielleicht zu zeigen warum es gut ist. Es gibt etliche Regeln dazu. Ich hoffe es hilft weiter.

P.s. ich hab das mal schnell auf dem Handy getippt also hoffe ich das es lesbar ist
 

Zurück
Oben