Generics sind zu streng - oder ich zu naiv?

  • Themenstarter Themenstarter Gonzo17
  • Beginndatum Beginndatum
G

Gonzo17

Gast
Servus,

irgendwie bin ich auf ein Konstrukt gestoßen, das ich nicht ganz verstehe. Es geht um Generics, die habe ich vorher selten aktiv verwendet und hab jetzt einen Fall, in dem es durchaus Sinn macht. Allerdings stoße ich dabei an ne Grenze, die ich nicht ganz verstehe. Im Folgenden erläutere ich mal anhand eines Beispiels, wo mein Problem liegt.

Zu Beginn habe ich eine abstrakte Klasse definiert, die ich für alle späteren Implementierungen benutzen möchte. Das sieht so aus:

Java:
public abstract class Abstract<T extends Type> {
	public void doSomethingWith(T object) {
		System.out.println(object);
	}
}

Dabei ist
Code:
Type
selbst ein Interface, da es eben auch verschiedene Typen geben kann. In diesem Fall ist es nicht wichtig, was dieses Interface wirklich bereitstellt, deswegen sieht es für das Beispiel so aus:

Java:
public interface Type {
}

Für die verschiedenen Typen erstelle ich Enums, die dann eben Parameter verwalten. Das sieht so aus:

Java:
public enum Type1 implements Type {
	A, B, C;
}

Und die Implementierung selbst schaut folgendermaßen aus:

Java:
public class Implementation1 extends Abstract<Type1> {
}

Dass hier auch nichts weiter geschieht, ist erstmal egal, es geht nur darum, dass die vererbte Methode eben nicht
Code:
Type
, sondern wirklich
Code:
Type1
erwartet! Ginge es beispielsweise um Getter und Setter, dann könnte ich sicherstellen, dass hier nur gewollte, konkrete Subtypen des allgemeinen Interfaces verwendet werden.

Jetzt wird es interessant. An einer anderen Stelle möchte ich nun ein Objekt erzeugen, das auf jeden Fall vom Typ
Code:
Abstract
ist. Ob es jetzt
Code:
Implementation1
oder
Code:
Implementation2
ist, kann ich nicht sagen, ist mir aber auch relativ egal. Sieht dann in etwa so aus:

Java:
public class Test {
	public static void main(String[] args) {
		Abstract<?> var = giveMeImplementationForAbstract();
		Type test = Type1.A;
		var.doSomethingWith(test);
	}
	
	public static Abstract<?> giveMeImplementationForAbstract(){
		return new Implementation1();
	}
}

Aber Zeile 5 verbietet er mir:
The method doSomethingWith(capture#2-of ?) in the type Abstract<capture#2-of ?> is not applicable for the arguments (Type1)

Macht auch keinen Unterschied, wenn ich statt
Code:
<?>
sowas wie
Code:
<? extends Type>
schreibe. Und wo genau ist das Problem?

Nun, so richtig verstehe ich die Ursache des Fehlers nicht. Deswegen frage ich euch. Irgendwo muss ich einen Denkfehler haben. Denn eigentlich würde ich erwarten, dass diese Methode ein Argument vom Typ
Code:
Type
annehmen würde. Tut sie aber nicht. Was genau soll denn da stehen? Kennt jemand einen eleganten Weg das anders zu lösen?
 
Zuletzt bearbeitet von einem Moderator:
du definierst doch, dass die Methode doSomethingWith() nur genau mit dem generischen Parameter T aufgerufen werden darf,
dieser ist unbekannt,

warum sollten beliebige Type-Objekte akzeptiert werden? wenn ja, dann ändere den Parametertyp der Methode von T auf Type (*)

edit:
> es geht nur darum, dass die vererbte Methode eben nicht Type , sondern wirklich Type1 erwartet!

hmm, das scheint dir bewußt und nicht das Ziel zu sein

> Denn eigentlich würde ich erwarten, dass diese Methode ein Argument vom Typ Type annehmen würde.

????

die (überschriebene) Methode ändert jedenfalls nicht ihre Erwartungen, je nachdem welchen Typ die Variable des Objektes gerade hat,
es wird entweder genau der Vertrag der Klasse eingehalten, der mit dem Interface übereinstimmen kann,
oder sich bei <?> rausgeredet dass man eben 'nichts genaues weiß man nicht', also nichts übergeben kann

edit2:
ok, überschrieben wird ja nicht, aber das kann Java an dieser Stelle nicht wissen, bzw. ergibt kaum Sinn, siehe (*) oben,
du kannst durch Casten Type durchaus übergeben:


also einfach nur
Java:
Abstract var = giveMeImplementationForAbstract();

oder

Abstract<Type> var = (Abstract)giveMeImplementationForAbstract();
generisch falsch ist es letztlich immer, ich sehe nun aber deinen Punkt, nach vielen edits 😉
 
Zuletzt bearbeitet von einem Moderator:
generisch falsch ist es letztlich immer, ich sehe nun aber deinen Punkt, nach vielen edits 😉

Hab brav abgewartet, wann es wohl ein Ende nimmt mit den Edits 😀

Gut, bzw nicht gut. Ich will ja Generics richtig verwenden und nicht "irgendwie". Wenn ich so wie du vorgeschlagen hast die Type Argumente weglasse, dann gibts ein unschönes Warning:

Abstract is a raw type. References to generic type Abstract<T> should be parameterized

Edit: Übrigens gleiches Problem beim zweiten Vorschlag, ein "unchecked cast" wäre das in dem Fall.
 
Zuletzt bearbeitet von einem Moderator:
wie gesagt ist das ein Workaround unter Umgehung aller Sicherheitsmaßnahmen, wenn du selber weißt dass es gut geht,
damit kann man in Teufels Küche kommen, etwa wenn Implementation1 die Methode tatsächlich überschreibt, dann mit Type1 als Parameter,
und ein Objekt von [c]Type2 implements Type[/c] übergeben wird
-> ClassCastException
zu empfehlen ist es also ganz und gar nicht,

du musst dich genau an die vorliegenden Informationen halten, dann meckert Java nicht, dann geht alles:
a) entweder bekommt doSomethingWith() Type statt T als Parameter, Generics fällt aus
b) oder aber es muss zwingend ein Objekt vom genau richtigen generischen Typ T übergeben werden, da gibt es keine Ausnahmen, warum auch?

ich sehe inzwischen zwei kleine Varianten, die du im Sinn haben könntest?:
- möchtest du beliebige Type-Objekte übergeben? wie gesagt a) zu empfehlen, ganz einfach, wenn T bleibt, sehe ich schwarz

- oder meinst du dass du ja mit Type1 durchaus das richtige Objekt hast, also diese Übergabe klappen müsse auch wenn an dieser Stelle für Java nicht alle Informationen vorliegen?
nun, dann gilt, dass da nichts zu retten ist,
ist einmal die Generics-Kette unterbrochen, kann sie quasi nicht respektabel repariert werden,

immer bleibt die Unsicherheit, dass falsche Objekte ganz genauso wie richtige Objekte behandelt werden,
es gibt auch nur schlecht Möglichkeiten, den tatsächlichen generischen Parameter auszulesen,
wobei bei festen Klassen wie Implementation1 eher möglich

ein nicht (ausreichend) generisches Objekt wieder auf Abstract<Irgendwas> zu casten ergibt IMMER eine Warning,
je nachdem wie sicher man sich selber ist, kann man sie ignorieren oder eben nicht
 
Zuletzt bearbeitet von einem Moderator:

Neue Themen


Zurück
Oben