Generics

Ich glaube bald treiben mich Java Generics in den Wahnsinn.

Also ich habe eine Hierarchie aus Klassen/Interfaces:


Linkable
  Label <
  Task
    GeneralTask <
    AssetTask
      TextureTask <
      ModelTask <

Mit “<” gekennzeichnete Klassen können instanziert werden (kein Interface/abstrakt).
DIese Instanzen werden in einem Singleton gespeichert. Das Singleton hat pro Klasse ein Set, um die jeweiligen Objekte zu speichern, sprich:

final private Set<GeneralTask> generalTasks = new HashSet<>();
final private Set<TextureTask> textureTasks = new HashSet<>();
final private Set<ModelTask> modelTasks = new HashSet<>();
final private Set<Label> taskLabels = new HashSet<>();

(In echt gibt es noch ein paar mehr direkte Subklassen von Linkable => analog zum Set)

Soweit sogut. Nun soll aber der Zugriff auf die Daten unter anderem durch Übergabe der Klasse (Class) möglich sein. Eine Methode soll also z.B. ein Objekt vom Typ Class erhalten und daraufhin die ModelTasks rausrücken. Wenn die Oberklassen AssetTask oder Task angefordert werden, sollen alle Elemente der Unterklassen rausgerückt werden.
Außerdem muss beim Löschen und Hinzufügen von Tasks entsprechende Callbacks ausgeführt werden, denen die gelöschten/hinzugefügten Tasks übergeben werden. Das Interface sieht vereinfacht so aus:

@Service
public interface DataAccess {

	public <T extends Linkable> Collection<T> getList(Class<T> clazz);
	public <T extends Linkable> boolean add(T data);
	public <T extends Linkable> boolean remove(T data);
	public <T extends Linkable> boolean addAll(Class<T> clazz, Collection<T> data);
	public <T extends Linkable> void removeAll(Class<T> clazz);
	public <T extends Linkable> void removeAll(Collection<T> data);
	
	public static interface TaskUpdateCallback {
		public <T extends Task> void onAdd(T task);
		public <T extends Task> void onAddAll(Collection<T> tasks, Class<T> type);
		public <T extends Task> void onRemove(T task);
		public <T extends Task> void onRemoveAll(Collection<T> tasks);
	}
	public void registerForUpdates(TaskUpdateCallback onUpdate);
}

Meine Implementation ist in Bezug auf Generics ein ziemlicher Alptraum. Entweder habe ich einiges nicht verstanden, oder bounded wildcards sind mehr Aufwand als Nutzen.

Erstes Problem: getList

	public synchronized <T extends Linkable> Collection<T> getList(Class<T> clazz) {
		if(clazz.equals(Task.class)) {
			Collection<T> allTasks = new HashSet<>();
			allTasks.addAll((Collection<T>)generalTasks);
			allTasks.addAll((Collection<T>) textureTasks);
			allTasks.addAll((Collection<T>) modelTasks);
			return allTasks;
		}
		else if(clazz.equals(AssetTask.class)) {
			Collection<T> assetTasks = new HashSet<>();
			assetTasks.addAll((Collection<T>) textureTasks);
			assetTasks.addAll((Collection<T>) modelTasks);
			return assetTasks;
		}
		else {
			return (Collection<T>) this.<T>getDataList(clazz).copy();
		}
	}

	private <T extends Linkable> Collection<T> getDataList(Class<T> clazz) {
		try {
			switch(clazz.getSimpleName()) {
				case "GeneralTask":
					return (Collection<T>) generalTasks;
				case "TextureTask":
					return (Collection<T>) textureTasks;
				case "ModelTask":
					return (Collection<T>) modelTasks;
				case "Label":
					return (Collection<T>) taskLabels;
				default:
					throw new UnsupportedOperationException();
			}
		} catch (UnsupportedOperationException e) {
			logger.error("
Database Error: Request for class type: "+clazz.getSimpleName()+" not implemented!
");
			throw new UnsupportedOperationException();
		}
	}

Geht das auch ohne Warnungen?

Zweites Problem removeAll:
Wenn etwas entfernt wird, soll geprüft werden, ob die Liste mit “etwas” eine Liste aus Tasks ist (oder Subklassen). Wenn ja, müssen die Tasks vor dem löschen gesammelt und and den entsprechenden callback übergeben werden. Ein NICHT kompilierendes Beispiel:

	// nur ums kompilierbar zu machen hier eine leere implementation, ich echt ist die nicht leer.
	final private TaskUpdateCallback callbacks = new TaskUpdateCallback() {
		@Override public <T extends Task> void onRemoveAll(Collection<T> tasks) {}
		@Override public <T extends Task> void onRemove(T task) {}
		@Override public <T extends Task> void onAddAll(Collection<T> tasks, Class<T> clazz) {}
		@Override public <T extends Task> void onAdd(T task) {}
	};

	public synchronized <T extends Linkable> void removeAll(Class<T> clazz) {
		Collection<T> removed = getList(clazz);
		if(clazz.equals(Task.class)) {
			generalTasks.clear();
			textureTasks.clear();
			modelTasks.clear();
		}
		else if (clazz.equals(AssetTask.class)) {
			textureTasks.clear();
			modelTasks.clear();
		}
		else {
			getDataList(clazz).clear();
		}
		if (clazz.isAssignableFrom(Task.class)) {
			callbacks.onRemoveAll(removed); //< Compiler Error
		}
	}

Das würde naütlich kompilieren, wenn man etwas hin und her castet. Gibt es da einen “schönen” Weg?

Mal ganz dumm gefragt: Um wieviele Objekte geht es? Wenn es nicht gerade hunderttausende sind, wäre vielleicht ein einziges Set besser, dass du entsprechend filterst (ich setze einmal Java 8 voraus):


//gegeben sei Class<T> clazz mit T extends Linkable

Set<T> tSet = linkableSet 
                .stream()
                .filter(clazz::isInstance)
                .map(clazz::cast)
                .collect(Collectors.toSet());

Weitere Möglichkeiten:

  • Generics sind nicht das richtige Mittel, um das zu erreichen, was du erreichen willst
  • Das Typsystem von Java ist nicht mächtig genug, um das abzubilden, was du willst

Das ganze klingt etwas wirr und (vor allem das mit dem Singleton) etwas suspekt. Vielleicht wäre es gut, mal ein paar Zeilen Code zu sehen, mit Erklärungen, WAS dort eigentlich gemacht werden soll - entweder Pseudocode, oder (auf Basis des schon geposteten) etwas compilierbares, wo man die Verwendung sieht.

Ansonsten:

Landeis Vorschlag klingt erstmal nahe liegend. Vielleicht gibt es Gründe, dass du das nicht gemacht hast - etwa, weil die Objekte aus irgendeinem Grund “getrennt gehalten” werden MÜSSEN. Aber das wäre noch zu klären.

Beim Überfliegen des Codes wunderten mich vor allem die vielen Parameterized Methoden - also, etwas oberflächlich gesagt: Warum

public interface DataAccess {
 
    public <T extends Linkable> Collection<T> getList(Class<T> clazz);
    public <T extends Linkable> boolean add(T data);
    public <T extends Linkable> boolean remove(T data);
    public <T extends Linkable> boolean addAll(Class<T> clazz, Collection<T> data);
    public <T extends Linkable> void removeAll(Class<T> clazz);
    public <T extends Linkable> void removeAll(Collection<T> data);

und nicht

public interface DataAccess<T extends Linkable> {
 
    public Collection<T> getList();
    public boolean add(T data);
    public boolean remove(T data);
    public boolean addAll(Collection<T> data);
    public void removeAll();
    public void removeAll(Collection<T> data);

(ja, das geht nicht 1:1, aber…)
…die meisten Typparameter sind da vermutlich (!) schlicht überflüssig, da
public <T extends Linkable> boolean remove(T data);
vermutlich auch einfach als
public boolean remove(Linkable data);
geschrieben werden könnte: Der Aufrufer legt fest, welcher Typ das ist, und die einzige Bedingung ist, dass es ein “Linkable” ist.

Hm.

Etwas, was schwierig sein könnte, ist, wenn die Callbacks ihre Aufrufe getypt erhalten sollen. Dann müssen sie auch einen Typparameter haben, und das zieht sich dann alles so eklig durch… Aber ich denke, dass durch die von Landei angedeutete Lösung, vielleicht auch als eine Utility-Funktion wie

public static <T> Set<T> filterByType(Class<T> type, Collection<?> collection) { ... };

void useIt()
{
    Set<Linkable> linkables = getThem();

    Set<ModelTask> modelTasks = filterByType(ModelTask.class, linkables);
}

schon viele Fälle abgedeckt werden könnten (falls das nicht irgendwelche Performanceprobleme verursacht - aber auch dann könnte man sich sicher was schöneres überlegen, als die aktuelle Lösung…)

@Landei
Dort wo die Software eingesetzt wird wahrscheinlich maximal 5k Task-Objekte, auslegen würde ich das ganze auf ca. 20k Objekte. Es kommt mir aber halt etwas komisch vor, die unterschiedlichsten Objekte in einen Topf zu werfen, so nach dem Motto: hier ist ein Set, ihr könnt euch euer Zeug rausfischen, User sind da auch irgendwo drin : D

Außerdem fordert die Logik sehr freizügig typgleiche Objekte an, weil sie davon ausgeht, dass das quasi kostenlos ist von der performance her.

Letztendlich geht das sicher in der einen oder anderen Form (z.B. alle Tasks in einen Topf, User extra), aber ich wollte einfach mal generell wissen, ob man das prinzipiell schön hinbekommen kann, wenn man mehrere Container verwendet.

Kondensiert:
public <T extends S> void fooBar(Container<T> list, Class<T> clazz);

Wenn ich dann im Methodenkörper durch Prüfen von clazz zur Laufzeit weiß, dass T Unterklasse von F ist (und F ist Unterklasse von S), dann müsste ich doch rein logisch list zu Container bzw. Container<? extends F> casten können müssen. Aber dafür ist wohl der Compiler einfach nicht schlau genug, obwohl er es bei einzelnen Elementen hinbekommt, wenn im Code instanceof o.ä. auftaucht (bei einzelnen Elementen kapiert der Compiler, dass zur Laufzeit nix schiefgehen KANN wegen der Logik und meckert deshalb nicht).

 @marco 

Also was kompilierbares ist halt schwierig wegen der ganzen Abhängigkeiten. Rein theoretiosch müsste das, was ich gepostet habe schon kompilierbar sein, wenn man Linkable + Unterklassen sowie die restlichen Methoden von DataAccess mockt. Kann ja alles leer sein.

Zu deinem Klassenparameter:
Du kannst den Parameter nicht in die Klasse hauen, weil ja jeder Methodenaufruf einen anderen Typparameter haben kann. Sprich sowas muss möglich sein:

Collection<Label> foo = dataAccess.<Collection<Label>>getList(Label.class);
Collection<Task> bar = dataAccess.<Collection<Task>>getList(Task.class);

Die Klassenargumente brauchst du wegen type erasure, zur Laufzeit weißt du ja nicht was grad angeliefert in der Collection wird. Außer du iterierst durch jedes Element um den Typ zu prüfen. Genau das soll ja meine Implementation verhindern und genau das macht Landeis Vorschlag.

Ich meine immernoch, dass
public <T extends Linkable> boolean add(T data);
keinen Sinn macht, und man genausogut
public boolean add(Linkable data);
schreiben könnte. Aber vielleicht bin ich auch nur übermüdet. Letzteres ist auch der Grund, weswegen alles weitere mit Vorsicht zu genießen ist, aber … vielleicht könnte man zumindest die verschiedenen Arten, die es gibt, unter einen Hut bringen (und dabei verallgemeinern). Also, grob im Sinne von sowas wie


import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

interface Linkable {}
abstract class Task implements Linkable {}
abstract class AssetTask extends Task {}

class Label implements Linkable {}
class GeneralTask extends Task {}
class TextureTask extends AssetTask {}
class ModelTask extends AssetTask {}
/**
 * 
Linkable
  Label <
  Task
    GeneralTask <
    AssetTask
      TextureTask <
      ModelTask < *
 */

class TypeSetManager
{
    private final Map<Class<?>, Set<?>> map;
    
    TypeSetManager()
    {
        map = new LinkedHashMap<Class<?>, Set<?>>();
    }
    
    <T> void add(Class<T> c, T object)
    {
        @SuppressWarnings("unchecked")
        Set<T> set = (Set<T>) map.get(c);
        if (set == null)
        {
            set = new LinkedHashSet<T>();
            map.put(c, set);
        }
        set.add(object);
    }
    
    void remove(Class<?> c, Object object)
    {
        Set<?> set = map.get(c);
        set.remove(object);
        if (set.isEmpty())
        {
            map.remove(c);
        }
    }
    
    @SuppressWarnings("unchecked")
    <T> Collection<T> getAll(Class<T> c)
    {
        Collection<T> result = new LinkedHashSet<T>();
        for (Entry<Class<?>, Set<?>> entry : map.entrySet())
        {
            Class<?> key = entry.getKey();
            if (c.isAssignableFrom(key))
            {
                Set<?> value = entry.getValue();
                result.addAll((Collection<? extends T>) value);
            }
        }
        return result;
    }
    
}


public class TaskManager
{
    
    public static void main(String[] args)
    {
        
        TypeSetManager t = new TypeSetManager();
        
        t.add(GeneralTask.class, new GeneralTask());
        t.add(Label.class, new Label());
        t.add(TextureTask.class, new TextureTask());
        t.add(ModelTask.class, new ModelTask());
        
        Collection<Linkable> c0 = t.getAll(Linkable.class);
        Collection<Task> c1 = t.getAll(Task.class);
        Collection<ModelTask> c2 = t.getAll(ModelTask.class);
        
        System.out.println(c0);
        System.out.println(c1);
        System.out.println(c2);
    }
    
}

(Ja, es gibt dort unchecked casts - aber es ist eben so implementiert, dass es sicher ist…)

Ja theoretisch braucht man für einzelne Elemente keine Typparameter, aber hier mal meine add Methode:

	public synchronized <T extends Linkable> boolean add(T data) {
		@SuppressWarnings("unchecked")
		boolean result = this.<T>getDataList((Class<T>)data.getClass()).add(data);
		if(data instanceof Task) {
			Task task = (Task) data;
			taskLabels.add(new Label(task.getLabel()));
			callbacks.onAdd(task);
		}
		return result;
	}

Da brauche ich den Typparameter, weil ich damit getDataList (siehe erster Post) aufrufe. Bei dem Aufruf beschwert sich sonst der Compiler bzw. ich müsste getDataList umbauen. Die Generics krachen halt immer irgendwo.

Zu deinem Code: Du hast halt im Prinzip aus meiner “getDataList” Methode (fischt für die richtige Klasse das richtige Set heraus) den TypeSetManager gemacht. Das macht Sinn, solange die Types keine Spezialfälle brauchen. Aber bei mir muss man für “Task” die akkumulierten Subklassenobjekte bekommen (ähnlich wie für AssetTask). Letztendlich muss man also sowieso mehr oder weniger manuell den Typ switchen. Der Compiler ist immer noch genauso blöd wie vorher, und mekkert halt immer noch rum^^

Der gesamte Code ist hier: https://github.com/Katharsas/GMM/blob/master/src/main/java/gmm/service/data/DataBase.java
Das Wesentliche ist aber schon im Thread denke ich.

Vlt. könnte man eine Methode bauen, die Collections wildcards auf einen spezielleren bound castet, um die warnings zu isolieren, in etwas so:

	private <F, T> Collection<? extends F> cast(Class<F> targetBound, Class<T> clazz, Collection<T> data) {
		if (clazz unterklassevon targetBound) return (Collection) data;
		else throw new ClassCastException...
	}

Es kommt mir aber halt etwas komisch vor, die unterschiedlichsten Objekte in einen Topf zu werfen, so nach dem Motto: hier ist ein Set, ihr könnt euch euer Zeug rausfischen, User sind da auch irgendwo drin : D

Die Frage ist, ob ein “Zusammenwürfeln” der Sets mit addAll wirklich schneller ist. Ich würde erst einmal schauen, ob es wirklich ein Performanceproblem mit meiner Variante gibt, dann würde ich - zumindest auf einem Multicore-Rechner - erst einmal stream() durch parallelStream() ersetzen und nochmal messen, und erst wenn das nicht hilft nach anderen Lösungen schauen.

Ansonsten: Guava hat ein ClassToInstanceMap-Interface mit Implementierungen, fast das was du brauchst. Du könntest dir den Code ein wenig umbiegen, um eine ClassToInstanceMultimap draus zu machen.

[quote=Katharsas]

Linkable
  Label <
  Task
    GeneralTask <
    AssetTask
      TextureTask <
      ModelTask <[/quote]

Geht Deine Klassenhierarchie evtl. auch flacher/weniger? Der Tipp mag zwar akademisch erscheinen, könnte Deine Probleme aber allgemein etwas entschärfen.

Das wird in der “getAll” gemacht - eben zumindest ohne händisch nochmal die Vererbungshierarchie darin zu encoden, welche "addAll"s man aufruft.

[QUOTE=nillehammer][/Code
Geht Deine Klassenhierarchie evtl. auch flacher/weniger? Der Tipp mag zwar akademisch erscheinen, könnte Deine Probleme aber allgemein etwas entschärfen.[/QUOTE]
Nein, und ich behaupte mal ide ist auch nicht wirklich tief. Linkable ist ja nur ein Interface.

[QUOTE=Landei;124541]Die Frage ist, ob ein “Zusammenwürfeln” der Sets mit addAll wirklich schneller ist. Ich würde erst einmal schauen, ob es wirklich ein Performanceproblem mit meiner Variante gibt, dann würde ich - zumindest auf einem Multicore-Rechner - erst einmal stream() durch parallelStream() ersetzen und nochmal messen, und erst wenn das nicht hilft nach anderen Lösungen schauen.

Ansonsten: Guava hat ein ClassToInstanceMap-Interface mit Implementierungen, fast das was du brauchst. Du könntest dir den Code ein wenig umbiegen, um eine ClassToInstanceMultimap draus zu machen.[/QUOTE]

Also was ein zusätzlicher Pluspunkt fürs Filtern wäre:
Ich muss später für die Anzeige im Frontend sowieso nochmal durchfiltern (der User hat eine Menge FIlter- und Suchmöglichkeiten, die die Ergebnislistenlänge reduzieren). Das heißt im schlimmsten Fall wird sowieso iterativ nochmal durchgefiltert und anschließend nochmal gesortet, und dabei gibts bisher keine Performanceprobleme. Wenn ich allerdings mein DataAcess Interface behalten möchte, kann ich nicht das zweite FIltern in das Filtern nach Klasse integrieren (wodurch ich ingesamt nur noch einmal filtern müsste). Vlt. mach ich mal nen Microbenchmark, aber ich erwarte nicht, das filtern wirklich merklich langsam ist.
Evtl. könnte DataAcces nen View/Stream zurückgeben statt die gefilterten Daten, der es erlaubt, Filterregeln hinzuzufügen und dann in einem Rutsch auszuführen.

Alternative wäre noch die ClassToInstanceMultimap, die ja auch @Marco13 mehr oder weniger selbst gebastelt hat, inklusive getAll (hab ich vorher wohl übersehen). WÜrde ich dann eher ählich wie MArco13 machen, um die spezifischen Funktionen zu bekommen. Diese Lösung gefällt mir von der Struktur her besser, irgendwie habe ich das Gefühl, gewisse Strukturen meiner Daten (wie Typstruktur) in der Speicherstruktur abbilden zu müssen, keine Ahnung woher das Gefühl kommt, aber das ist der Grund, wieso ich die “Topf”-Lösung etwas unangenehm finde.

Edit: Die User müsste man sowieso extra machen, ich möchte nur ungern aus einer Liste mit max. 20.000 Objekten die maximal ca. 50 User rausfiltern müssen.

Edit2: Mich würden auch noch Kommentare zu meiner Frage in Post #4 interessieren, ob Generics/Compiler nun mal so eingeschränkt sind dass das nicht geht (unter “kondensiert”).

Ein “F” kommt dort gar nicht vor. Kannst du die Frage nochmal (“weniger kondensiert” ;)) Als eine Klasse mit einer Methode und einem Dummy-Aufruf dieser Methode beschreiben?

EDIT: Wenn man das, was dort gemacht werden soll, über ein Interface beschreiben kann, hat man ggf. auch mehr Möglichkeiten. Im speziellen kann man sich die Implementierung aussuchen. Ganz konkret könnte man ggf. “Laufzeit” gegen “Speicherverbrauch” tauschen: Man KÖNNTE den “TypeSetManager” auch so schreiben, dass er die relevanten Klassen kennt, und bei einem “add” die Objekte zu allen Sets hinzugefügt werden, wo sie hingehören. Im Pseudocode:

List<Class<?>> classes = Arrays.asList(Task.class, ModelTask.class);

Map<Class<?>, Set<?>> map;

<T> void add(Class<T> c, T object)
{
    for (Class<T> key : classes) 
    {
        if (key.isAssignableFrom(c))
        {
            map.get(key).add(object);
        }
    }
}

<T> Collection<T> getAll(Class<T> c) 
{
    return map.get(c);
}

Also grob: Dass das “Zusammenbauen” der Menge der Objekte für eine Klasse nicht beim “getAll” gemacht wird, sondern jeweils beim Hinzufügen - indem man das Objekt in alle Mengen, zu denen es gehört, einfügt.

[QUOTE=Marco13]Ein “F” kommt dort gar nicht vor. Kannst du die Frage nochmal (“weniger kondensiert” ;)) Als eine Klasse mit einer Methode und einem Dummy-Aufruf dieser Methode beschreiben?
[/QUOTE]

public class Foo extends Super {}
public class SubFoo1 extends Foo {}
public class SubFoo2 extends Foo {}

public class MyClass {

public <T extends Super> void fooBar(Container<T> list, Class<T> clazz) {
    //do something with list
    if (Foo.class.isAssignabeFrom(clazz)) {    //if container is full of Foos
        Container<T extends Foo> fooList = list;
        //pass it to a function that requires Container<? extends Foo>
    }
}

public void callFooBar() {
    Container<SubFoo1> c1 = ...;
    Container<SubFoo2> c2 = ...;
    COntainer<Super> s = ...;

    foobar(c1, SubFoo1.class);
    foobar(c2, SubFoo2.class);
    foobar(s, Super.class);
}
}

Das Szenario funktioniert mit einzelnen Elementen (ohne Container) problemlos dank instanceOf.
Edit: iM prinzip möchte ich einen lower wildcard bound downcasten, und zwar ohne Warnung, wenn der Source garantiert dass das sicher ist.

[QUOTE=Marco13;124600]
Also grob: Dass das “Zusammenbauen” der Menge der Objekte für eine Klasse nicht beim “getAll” gemacht wird, sondern jeweils beim Hinzufügen - indem man das Objekt in alle Mengen, zu denen es gehört, einfügt.[/QUOTE]

Jop, das kann man dann natürlich zusätzlich auch machen. Referenzen sollten ehh nicht so viel Speicher kosten.

Legst du nur Objekte eines Typs in die Liste, oder ist das eine heterogene Liste? Ersteres sollte eigentlich mit Generics schon beim Einfügen erreicht werden, sd nur Objekte des bestimmten Typs wieder entfernt werden können, Zweiteres ausgeschlossen wird.

Hä? Was da jetzt drin ist ist doch völlig egal für die Compilerwarnung?
Aber du kannst vor mir aus von jeweils homogenen Listen ausgehen…

War doch nur eine allgemeine Frage. Eine heterogene Liste macht Generics überflüssig, aber auch führt zu (erheblich) mehr Operationsaufwand. Das Thema ist lang und knn ich eben nicht in 5 Min. lesen.

EDIT: @CyborgBeta Hast du schonmal die Option in Betracht gezogen, dich irgendwo einfach rauszuhalten? kopfschüttel

Also etwa so

KSKB
[spoiler]

import java.util.ArrayList;
import java.util.Collection;

class Super
{
}

class Foo extends Super
{
}

class SubFoo1 extends Foo
{
}

class SubFoo2 extends Foo
{
}

public class ClassBasedCollections
{
    public static void main(String[] args)
    {
        ClassBasedCollections c = new ClassBasedCollections();
        c.callFooBar();
    }
    
    private Collection<? extends Foo> foos;
    
    public <T extends Super> void fooBar(Collection<T> list, Class<T> clazz)
    {
        if (Foo.class.isAssignableFrom(clazz))
        {
            Collection<? extends Foo> fooList = (Collection<? extends Foo>) list;
            processFoos(fooList);
        }
    }

    private <T extends Foo> void processFoos(Collection<T> foos)
    {
        System.out.println("Processing foos: "+foos);
        this.foos = foos;
    }

    public void callFooBar()
    {
        Collection<SubFoo1> c1 = new ArrayList<SubFoo1>();
        c1.add(new SubFoo1());
        Collection<SubFoo2> c2 = new ArrayList<SubFoo2>();
        c2.add(new SubFoo2());
        Collection<Super> s = new ArrayList<Super>();
        s.add(new Super());

        fooBar(c1, SubFoo1.class);
        fooBar(c2, SubFoo2.class);
        fooBar(s, Super.class);
    }
}

[/spoiler]

Ich denke nicht, dass das ohne Warnings geht. Erst dachte ich, dass es schlicht nicht sicher wäre (d.h. dass man dann eine ClassCastException provozieren könnte), hab’s aber auf die Schnelle nicht hingekriegt (ggf. müßte man nochmal drüber nachdenken). Es ist (vermutlich?) sicher, weil man sozusagen “nur den Bound castet” (und nicht den Typ selbst), d.h. man kann mit der Liste danach nichts machen, was die Typsicherheit verletzt (vermutlich!), aber das kann der Compiler ja nicht wissen. Sowas wie

        if (SomeCompletelyUnrelatedAndMaybeEvenJustObject.class.isAssignableFrom(clazz))
        {
            Collection<? extends Foo> fooList = (Collection<? extends Foo>) list;
            processFoos(fooList);
        }

würde/müßte ja auch compileren, und das kann natürlich beliebig schiefgehen…

Doch, klar, es sind ja genug Helfer da. Dein Thema.

@Marco13

Yo das mein ich. Das ist immer sicher, soweit ich das beurteilen kann. Ist natürlich nicht ganz trivial für den Compiler das zu erkennen, das stimmt, aber möglich wäre es allemal. Folgendes geht ja auch ohne Warnung, das schlägt in eine ähnliche RIchtung, nur ohne generische Methodenparameter:

Object o;
if (o.getClass().equals(Foo.class)) {
    Foo f = (Foo) o;
}

Da muss der Compiler ja auch möglicherweise recht komplexen Code analysieren (und Wissen über verschiedene spezifische Dinge besitzen), umd die Sicherheit zu sehen. Fände ich schön wenn der Compiler im gleichen Sinne auch mit meinem Beispiel umgehen könnte. Wäre nebenbei auch viel einfacher, wenn es keine type erasure gäbe, dann bräuchte man (in meinem Beispiel) nicht das extra Class Argument, naja, schön wäre vieles : D

Aber wenn man solche “bound casts” in eine util methode auslagert, kann man vlt. die warnings aus dem wichtigen Code raushalten.

Nun, dieser cast gibt nur keine Warnung, weil keine Generics involviert sind. Krachen könnte es da auch, wenn man auf den falschen Typ castet.

Insgesamt sehe ich im Moment keine wirklich schöne Lösung (ohne casts und supressed warnings). (Ob es im abstrakt-übergeordneten Sinne eine “schöne(re)” Lösung für das eigentliche Problem gibt, ist aber schwer zu sagen…)

Man kann auch die Methode Class.cast verwenden, wenn man einfach nur keine Warnung haben will. Heißt natürlich noch lange nicht, dass der Cast auch klappt :slight_smile: