Collections Sind Subklassen-Objekte in Listen mit Generics erlaubt?

Thor_es

Mitglied
Hallo zusammen,

bisher dachte ich:
  • dass einer typisierten Liste nur Objekte des angegeben Typs hinzugefügt werden können
  • dass ich abgeleitete Objekte upcasten muss, um sie der auf die Superklasse typisierten Liste hinzufügen zu können
  • beim Abruf aus der Liste nur Objekte des angegebenen Listentyps zurückgegeben werden (abgeleitete Objekte werden als Superklassentyp ausgegeben)
Nun habe ich folgendes Beispiel erstellt. Es gibt eine Superklasse "Animal" von der die Unterklassen "Cat" und "Dog" ableiten. Die Liste Tiere ist auf "Animal" typisiert. Ich kann nun alle Unterklassen-Objekte ohne Upcast hinzufügen (von ich dachte, dass es nicht funktioniert). Außerdem werden die "Cat"- und "Dog"-Objekte auch als diese aus der als "Animal" typisierten Liste zurückgegeben und die korrekten Methoden werden ausgeführt.

Das widerspricht meinem aktuellen Verständnis von Generics. Kann mir bitte jemand helfen, meinen Knoten im Kopf zu lösen.

Vielen Dank für eure Unterstützung.

[CODE lang="java" title="Beispielcode" highlight="10, 11, 12, 13, 14, 15"]import java.util.*;

public class Main
{
public static void main(String[] args) {
List<Animal> Tiere = new ArrayList<>();
Tiere.add(new Cat());
Tiere.add(new Dog());
Tiere.add(new Animal());
System.out.println(Tiere.get(0).getClass()); //Output: class Cat
System.out.println(Tiere.get(1).getClass()); //Output: class Dog
System.out.println(Tiere.get(2).getClass()); //Output: class Animal
Tiere.get(0).sound(); //Output: Miau
Tiere.get(1).sound(); //Output: Wuff
Tiere.get(2).sound(); //Output: GrrrrMiauWuffPiep
}
}

class Animal{
public void sound(){
System.out.println("GrrrrMiauWuffPiep");
}
}
class Cat extends Animal{
@Override
public void sound(){
System.out.println("Miau");
}
}
class Dog extends Animal{
@Override
public void sound(){
System.out.println("Wuff");
}
}[/CODE]
 
dass einer typisierten Liste nur Objekte des angegeben Typs hinzugefügt werden können
Das ist auch richtig, aber nehmen wir folgende Kette an:

Java:
public class A extends Object {}
public class B extends A {}
public class C extends C {}

public class X extends Object {}
public class Y extends X {}
public class Z extends Z {}

Dann sind *alle* diese Klassen ja ein Object, aber sie sind halt auch mehr. Wenn du diese jetzt zuweist:

Java:
C c = new C();

Object object = c;

// TODO Use "object" here.

Dann funktioniert dies ja auch ohne Probleme, weil sie eben von diesem Typ erben.

dass ich abgeleitete Objekte upcasten muss, um sie der auf die Superklasse typisierten Liste hinzufügen zu können
Nein.

beim Abruf aus der Liste nur Objekte des angegebenen Listentyps zurückgegeben werden (abgeleitete Objekte werden als Superklassentyp ausgegeben)
Ja, wenn es um den Compiler geht. Klassen von vorhin:

Java:
C c = new C();

List<A> listOfAs = new ArrayList<>();
listOfAs.add(c); // Keine Umwandlung notwendig, weil "C" ja auch "A" ist.

A retrievedC = litOfAs.get(0);

Du kannst natuerlich ein Objekt vom Typ C in die Liste packen, weil diese ja auch vom Typ [/ICODE]A[/ICODE]. Aber aus der Liste bekommst du nur den spezifizierten Typen heraus, weil der Compiler kann ja nicht wissen von welchen Subtypen die Werte sind, der Compiler kann nur garantieren dass diese vom Typ [/ICODE]A[/ICODE] sind.

Oder um es etwas anders zu veranschaulichen:

Java:
new A() instanceof A // true
new B() instanceof A // true
new C() instanceof A // true

new A() instanceof B // false
new B() instanceof B // true
new C() instanceof B // true

new A() instanceof C // false
new B() instanceof C // false
new C() instanceof C // true
 
Ja, wenn es um den Compiler geht. Klassen von vorhin:

Java:
C c = new C();

List<A> listOfAs = new ArrayList<>();
listOfAs.add(c); // Keine Umwandlung notwendig, weil "C" ja auch "A" ist.

A retrievedC = litOfAs.get(0);

Du kannst natuerlich ein Objekt vom Typ C in die Liste packen, weil diese ja auch vom Typ [/ICODE]A[/ICODE]. Aber aus der Liste bekommst du nur den spezifizierten Typen heraus, weil der Compiler kann ja nicht wissen von welchen Subtypen die Werte sind, der Compiler kann nur garantieren dass diese vom Typ [/ICODE]A[/ICODE] sind.

Daher bin ich auch davon ausgegangen, dass der Compiler, nachdem ich die Objekte aus der Liste hole, nicht mehr ermitteln kann, um welches Animal es sich genau handelt und ich über instanceof prüfen müsste, ob es sich um ein Objekt "Cat" oder "Dog" handelt. In meinem Codebeispiel konnte ich aber die Objekte ohne Aufwand als Typ Cat und Dog verwenden, obwohl die Liste als Animal typisiert ist. Kannst du mir das noch einmal erklären?
 
Daher bin ich auch davon ausgegangen, dass der Compiler, nachdem ich die Objekte aus der Liste hole, nicht mehr ermitteln kann, um welches Animal es sich genau handelt und ich über instanceof prüfen müsste, ob es sich um ein Objekt "Cat" oder "Dog" handelt.
Der Compiler kann das auch nicht mehr, aber zur Laufzeit ist die Instanz ja natuerlich weiterhin so wie sie erzeugt wurde. Als beim Aufruf:

Java:
C c = (C)listOfAs.get(0);

Kann dir der Compiler nicht garantieren dass das wirklich ein "C" ist welches du holst, aber zur Laufzeit ist die Instanz natuerlich so wie sie ist.

In meinem Codebeispiel konnte ich aber die Objekte ohne Aufwand als Typ Cat und Dog verwenden, obwohl die Liste als Animal typisiert ist.
Weil sich die *Instanz* nicht aendert, nur weil du sie irgendwohin castest. Das aendert weder die Instanz noch den Typ von der Instanz.

Wenn du eine Katze hast, und diese in einem Raum mit *anderen* Tieren steckst, bekommst ja auch wieder eine Katze heraus, und nicht *irgendein Tier*.
 
wenn du einen "cast" anwendest sagst du dem compiler eigentlich " trust me im an engineer" ... du sagst halt vertrau mir mit dem cast und behandle es wie wenn das so eines wäre ,,die Urpsrungliche Klasse von der ein Objekt herkommt darf nicht verändert werden
 
Das ist eine falsche Annahme, du verwendest die Instanzen *als* Animal Instanzen, aber es sind Instanzen von den Typen Cat und Dog.
da du weist dass jedes animal eine sound methode hat kannst du egal was du von dem vererbungs baum bekommst die methode aufrufen ... was in der methode drin steht ist das problem des objektes ( bzw so wies in der jeweiligen klasse definiert worden ist )
 
OK. Wenn ich nun aber die "sound"-Methode aus der Oberklasse "Animal" entferne, erhalte ich beim Aufruf der entsprechenden Methoden bei "Cat" und "Dog" die folgende Fehlermeldung:

Screenshot.png

Dann muss ich wieder umständlich downcasten (siehe Codebeispiel), damit es funktioniert, obwohl die ursprünglichen Typen der Instanzen bekannt sind und auch die Methode getClass() die korrekten Subklassen ausgibt. Das kann ich noch nicht ganz nachvollziehen.

[CODE lang="java" title="geänderter Code" highlight="27, 14, 15, 17-22"]import java.util.*;

public class Main
{
public static void main(String[] args) {
List<Animal> Tiere = new ArrayList<>();
Tiere.add(new Cat());
Tiere.add(new Dog());
Tiere.add(new Animal());
System.out.println(Tiere.get(0).getClass()); //Output: class Cat
System.out.println(Tiere.get(1).getClass()); //Output: class Dog
System.out.println(Tiere.get(2).getClass()); //Output: class Animal

Tiere.get(0).sound(); //Output: error
Tiere.get(1).sound(); //Output: error

Animal tier1 = Tiere.get(0);
Cat katze = (Cat)tier1;
katze.sound();
Animal tier2 = Tiere.get(1);
Dog hund = (Dog)tier2;
hund.sound();
}
}

class Animal{

}
class Cat extends Animal{
public void sound(){
System.out.println("Miau");
}
}
class Dog extends Animal{
public void sound(){
System.out.println("Wuff");
}
}[/CODE]
 
das objekt wird behandelt als irgendwas beim vererbungs baum beginnnend ab animal, durch die generic Liste sagst du "egal was ich rein lege es is MINDESTENS ein ANimal"

dh wenn du get machst kriegst du irgendetwas das MINDESTENS ein Animal ist raus, das ist das einzige was die jvm zu dem zeitpunkt sicher weis
das objekt wird als ein Animal behandelt ( dh alle methodne die public und package sind von außen ) können aufgerufen werden aber der Inhalt dieser Methodne ist das problem des objektes wie bereits gesagt 🙂

wenn du im vererbungs baum beim animal eine public methode hast MÜSSEN alle unteren Klassen in der Vererbung diese Public methode auch haben dh jetzt kombinieren , "es ist mindestens ein animal und jedes animal kann dieses und jenes und alle drunter können das auch" deweswegen durftest du auch wei du sound noch in animal hattest die methode aufrufen durftest , der Inhalt der methode ist wie oben beschrieben das problem der Klasse/objektes

downcasts sind wie besagt "trust me im an engineer" für den Compiler... der Compiler vertraut dir da einfach mal weil der compiler nicht weis ob es funtkionieren wird wie erwartet, falls es wie erwartet funktioniert hast du am ende ein objekt das momentan als der cast gedeutet wird deswegen darfst du dann sound aufrufen weils dann als cat zb gedeutet wird

kurzgesagt... du deutest nach dem Get das object als animal aber ein animal kann keinen sound mehr weil das eine Spezielle methode IRGENDEINER unterklasse ist ... zu was soll den gecastet werden ? hund katze? ausprobieren ? zufall ? -> uneindeutigkeit darfs nicht geben
 
Klasse. Vielen Dank für die ausführliche Erklärung. So langsam erschließt es sich mir 😅

Nur noch eine kleine Rückfrage bezüglich des Cast-Syntax. Gibt es hier eine "schlanke" Variante direkt aus der Liste auf "Cat" oder "Dog" zu casten. Mit "Cat Katze = (Cat)Tiere.get(0)" hatte ich keinen Erfolg.
 

Zurück
Oben