Java generic fields

generics, java

Solution

Well the problem is that you need to retain your inheritance as generic type information.

Basically you would have to do something like:

class PlayerCharacter<P extends PlayerCharacter<P>> {
    List<Trait<? super P>> myTraits;
}

class VampirePlayer extends PlayerCharacter<VampirePlayer> {...}

abstract class Trait<P extends PlayerCharacter<P>> {
    abstract void apply(P player);
}

class FangLengthTrait extends Trait<VampirePlayer> {...}

It begins to get very clunky, though. You can somewhat improve the situation by approaching from composition:

class Attributes {}

class Base extends Attributes {
    int strength;
}

class Vampire extends Base {
    int fangLength;
}

class Player<A extends Attributes> {
    final A attributes;
    final List<Trait<? super A>> traits = new ArrayList<>();

    Player(A attributes) {
        this.attributes = attributes;
    }

    void applyTraits() {
        for(Trait<? super A> t : traits)
            t.apply(this);
    }
}

interface Trait<A extends Attributes> {
    void apply(Player<? extends A> player);
}

class StrengthTrait implements Trait<Base> {
    @Override
    public void apply(Player<? extends Base> player) {
        player.attributes.strength = 1000;
    }
}

class FangLengthTrait implements Trait<Vampire> {
    @Override
    public void apply(Player<? extends Vampire> player) {
        player.attributes.fangLength = 100;
    }
}

final class Factory {
    private Factory() {}

    public static Player<Base> newPlayer() {
        return new Player<Base>(new Base());
    }

    public static Player<Vampire> newVampire() {
        return new Player<Vampire>(new Vampire());
    }
}

I still find it clunky, personally. If you are mainly just using these `Trait`s to construct objects you might think about using a builder or factory so you don't need to use generics.

Problem

I have a PlayerCharacter class. PlayerCharacter can be extended (for example, VampirePlayerCharacter vs WerewolfPlayerCharacter) I have a Trait class. Trait can be extended (for example, Generation or Gnosis). `PlayerCharacter` has a method, `#withTrait(Trait)`, which adds the Trait to a collection. `PlayerCharacter` has a method, `#applyAllTraits()` which loops through the collection and applies each of them to the character. A `VampirePlayerCharacter` should be able to be given any `Trait` that could apply to a `PlayerCharacter`, as well as any `Trait` that could only apply to a `VampirePlayerCharacter`. So I added a generic type, making `Trait<PC extends PlayerCharacter>` Thus, there can be `BasicTrait<PlayerCharacter>` and `Generation<VampirePlayerCharacter>` My conundrum: If PlayerCharacter's collection of traits is `Collection<Trait<PlayerCharacter>>`, then `VampirePlayerCharacter` can't add a `Trait<VampirePlayerCharacter>` to the collection. If `PlayerCharacter`'s collection of traits is `Collection<Trait<? extends PlayerCharacter>>`, then `VampirePlayerCharacter` can add a `Trait<VampirePlayerCharacter>` to the collection. However, `PlayerCharacter` can no longer loop through the traits, because their type is indeterminate (it could be `Trait<PlayerCharacter>` or a `Trait<VampirePlayerCharacter>` or a `Trait<WerewolfPlayerCharacter>` or...) If `PlayerCharacter`'s collection of traits is `Collection<Trait<? super PlayerCharacter>>`, then `VampirePlayerCharacter` can't add a `Trait<VampirePlayerCharacter>`, because `VampirePlayerCharacter` isn't a supertype of `PlayerCharacter` I'm about a hair's-breadth from saying that more specialized traits just have to use a cast in their apply method and if you set things up inappropriately, they'll explode- but I'm certain that this is not a novel problem, and I just can't wrap my head around the solution. ``` class PlayerCharacter { private int str; List<Trait<?>> traits = new ArrayList<>(); PlayerCharacter withStrength(int str) { this.str = str; return this; } PlayerCharacter withTrait(Trait trait) { this.traits.add(trait); return this; } void applyTraits() { traits.forEach((Trait<?> t) -> t.apply(this)); } } class VampirePlayerCharacter extends PlayerCharacter { private int fangLength; VampirePlayerCharacter withFangLength(int fangLength) { this.fangLength = fangLength; return this; } } abstract class Trait<PC extends PlayerChracter> { void apply(PC pc); } class StrengthTrait extends Trait<PlayerCharacter> { private int str; StrengthTrait(int str) { this.str = str; } void apply(PlayerCharacter pc) { pc.withStrength(str); } } class FangLengthTrait extends Trait<VampirePlayerCharacter> { private int fangLength; FangLengthTrait(int fangLength) { this.fangLength = fangLength; } void apply(VampirePlayerCharacter pc) { pc.withFangLength(fangLength); } } ```

Original source