• Es freut uns dass du in unser Minecraft Forum gefunden hast. Hier kannst du mit über 130.000 Minecraft Fans über Minecraft diskutieren, Fragen stellen und anderen helfen. In diesem Minecraft Forum kannst du auch nach Teammitgliedern, Administratoren, Moderatoren , Supporter oder Sponsoren suchen. Gerne kannst du im Offtopic Bereich unseres Minecraft Forums auch über nicht Minecraft spezifische Themen reden. Wir hoffen dir gefällt es in unserem Minecraft Forum!

Java Generics für gleichen Wert

CubBossa

Schafhirte
Registriert
17 Juli 2015
Beiträge
125
Diamanten
369
Minecraft
CubBossa
Hallöchen da draußen :D
Ich bin seit gestern damit beschäftigt, unsere Menü Klassen etwas zu erweitern und eigentlich ist nurnoch eine Unschönheit übrig, mit der ich nicht fertig werde. Die Lage: Es gibt eine Parent Menüklasse "InventoryMenu" und, einfach gehalten, die beiden Childklassen OpenableMenu (Kisten, Trichter, Ofen, etc.) und HotbarMenu (die 9 Hotbarslots halt).

Java:
public abstract class InventoryMenu<T, A extends ActionContext<T>, B extends BackContext, C extends CloseContext> {}

public abstract class OpenableMenu extends InventoryMenu<ClickType, ClickContext, BackContext, CloseContext> {}

public class HotbarMenu extends InventoryMenu<HotbarAction<?>, TargetContext<HotbarAction<?>, ?>, BackContext, CloseContext> {}

Mit dieser Basis habe ich dann alle Methoden zum Setzen der ClickHandler geschrieben und das ganze funktioniert auch ziemlich gut. Probleme entstehen jedoch durch den Wildcast der HotbarAction. Die HotbarAction ist so aufgebaut:

Java:
public class HotbarAction<T> {
    public static final HotbarAction<Block> RIGHT_CLICK_BLOCK = new HotbarAction<>();
    public static final HotbarAction<Entity> RIGHT_CLICK_ENTITY = new HotbarAction<>();
    ...
}

Methoden in der InventoryMenu Klasse sind zum Beispiel so aufgebaut:
Java:
    public void setClickHandler(int slot, T action, @Nullable ContextConsumer<A> clickHandler) {}

Zuletzt wäre noch wichtig, den TargetContext zu zeigen, der ja die HotbarAction als Typ Argument hat:
Java:
public class TargetContext<A, T> extends ActionContext<A> {
    //A ist der Action Type, also bei Hotbarmenüs HotbarAction<T>. T ist das Target, also bei RIGHT_CLICK_BLOCK die Klasse "Block"
        private final T target;

    public TargetContext(Player player, ItemStack itemStack, int slot, boolean cancelled, A action, T target) {
        super(player, itemStack, slot, action, cancelled);
        this.target = target;
    }
}

So und jetzt zur eigentlichen Frage :D
In der Klassendefinition (Sorry, wenn ich hier vielleicht einige Fachwörter durcheinander bringe) der HotbarMenu Klasse kann ich ja keinen HotbarAction Typ wie Entity/Block/Item angeben, der soll ja für jeden ClickHandler spezifisch sein. Daher muss ich einen Wildcast nehmen.
Folglich wird die Beispielmethode die ich gesendet habe nicht wie in 1. sondern wie in 2. angelegt:

Java:
public <T> void setItemAndClickHandler(int slot, ItemStack item, HotbarAction<T> action, @Nullable ContextConsumer<TargetContext<HotbarAction<T>, T>> clickHandler) {}
public void setItemAndClickHandler(int slot, ItemStack item, HotbarAction<?> action, @Nullable ContextConsumer<TargetContext<HotbarAction<?>, ?>> clickHandler) {}

Wenn ich den ContextConsumer nun als Lamda anlege bei Methodenaufruf, dann gibt der TargetContext mir nicht zb "Entity" an, weil die im vorherigen Parameter angegebene HotbarAction den Typ <Entity> hat, sondern nur "capure of ?".
Also meine Methode wäre zb:
Java:
        menu.setClickHandler(0, HotbarAction.LEFT_CLICK_ENTITY, context -> {
            Entity e = context.getTarget(); //Hier verlangt er nun einen Cast.
        });

Wie als Kommentar geschrieben, muss ich nun den Target zu meinem Wunschtyp casten. Das Problem ist, ich programmiere die Menü Klassen, aber andere Entwickler sollen ja nur die Methoden aufrufen und müssen dann wild raten, welcher Typ das Target ist. Das ist ja nicht der Sinn und eigentlich ist alles da. Nur kann ich nicht angeben, dass die Wildcasts in " setClickHandler(int slot, HotbarAction<?> action, ContextConsumer<HotbarAction<?>> clickHandler)" vom gleichen Typ sind.
Ich hoffe, ich konnte mein Problem gut erklären und jemand hat eine Lösung oder Tips parat :D allein schon ein Hint, was ich googlen könnte wäre eine Hilfe ^^
Liebe Grüße und Danke im Voraus,
Leo / CubBossa
 
Zuletzt bearbeitet:

JOO200

Braumeister
Registriert
18 Dezember 2016
Beiträge
442
Diamanten
228
Ich verstehe nicht ganz, warum du die Methode nicht generisch definieren kannst. Dann müsste doch jeder Einsatz der Methode clickhandlerspezifisch sein.

Müsste dann sowas wie
public <T> setHandler(..., Action<T>, Consumer<? extends T>)
sein
 

CubBossa

Schafhirte
Registriert
17 Juli 2015
Beiträge
125
Diamanten
369
Minecraft
CubBossa
@JOO200 Die Methode liegt ja in der Parent Klasse und ich erstelle HotbarMenu ja mit HotbarAction<?> als einer der Typen, wodurch durch die Erbschaft halt die Methode
public void setHandler(..., Action<?>, Consumer<Context<HotbarAction<?>>>)
und nicht
public <T> void setHandler(..., Action<T>, Consumer<? extends T>)
entsteht

@SirYwell Was meinst du genau mit nachlässig? Also wo hätte ich sie lieber weiter durchziehen sollen?
 

CubBossa

Schafhirte
Registriert
17 Juli 2015
Beiträge
125
Diamanten
369
Minecraft
CubBossa
Aber muss ich da nicht wildcards verwenden? Weil der Typ der HotbarAction ist ja für jeden ClickHandler variabel und nicht einmal für das ganze HotbarMenu gesetzt. Das wäre ja zb eine HotbarAction<Block> oder HotbarAction<Entity>, damit ich mich aber nicht auf eins festlege nehme ich HotbarAction<?>.

Was ich im Moment durch die Vererbung habe ist:
Java:
    public void setClickHandler(int slot, HotbarAction<?> action, @Nullable ContextConsumer<TargetContext<HotbarAction<?>, ?>> clickHandler) {
        ...
    }
Was ich bräuchte, wäre die hier:
Java:
    public <D> void setClickHandler(int slot, HotbarAction<D> action, @Nullable ContextConsumer<TargetContext<HotbarAction<D>, D> clickHandler) {
        ...
    }
Damit dann für den Benutzer das hier möglich ist:
Java:
        HotbarMenu menu = new HotbarMenu();
        menu.setClickHandler(0, HotbarAction.LEFT_CLICK_ENTITY, context -> {
            Entity e = context.getTarget();
        });
ohne dass er aus dem Namen der HotbarAction erraten muss, welcher Typ getTarget() sein könnte.

Ich kann allerdings auch nicht einfach die Methoden überschreiben, weil die vererbte Map für die ClickHandler so aussieht:
Map<Integer, Map<HotbarAction<?>, ContextConsumer<TargetContext<HotbarAction<?>, ?>>>> clickHandlers
und nicht so:
Map<Integer, Map<HotbarAction<D>, ContextConsumer<TargetContext<HotbarAction<D>, D>>>> clickHandlers
was auch immer hier D wäre, weil es ja eben für jeden unterschiedlichen Eintrag variabel, aber für einen ganzen Eintrag gleich sein soll

Vielleicht ist das auch alles gar nicht möglich oder ich bin es ganz falsch angegangen. Ich bin viel am ausprobieren mit Generics, weil es noch ein neues Thema für mich ist. Aber für den Rest hat es ja eigentlich gut funktioniert
 

SirYwell

PlotSquared Entwickler
Registriert
30 Juni 2017
Beiträge
540
Diamanten
488
Minecraft
SirYwell
Du müsstest HotbarMenu<T, C, ...> (also alle Parameter, die du brauchst, um die Wildcards zu ersetzen) schreiben, und von dort aus weitergeben.

Im Allgemeinen kann ich dir aber aus Erfahrung sagen, dass man sich oft keinen Gefallen tut, wenn man so viele generische Parameter hat. Die Wahrscheinlichkeit ist groß, dass man plötzlich noch einen braucht oder dann doch an einer Stelle die Typen verloren gehen. Dann steht man vor einem Haufen Chaos, das einem kein bisschen mehr hilft.

Eventuell ist es in deinem Fall hilfreich, das ganze eher mit einem Visitor-ähnlichen Design anzugehen, und darüber die Funktionalität zu implementieren (falls einfache Abstraktion nicht mehr reicht, das wär natürlich noch einfacher und noch schöner).
 
Oben