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?