2

I got a generic interface with one method accepting a parameter of the generic type:

public interface ComponentRenderer<T extends GuiComponent> {
    public void draw(T component);
}

Furthermore I have an abstract class, that declares a variable of this interface type using a bounded wildcard:

public abstract class GuiComponent extends Gui {
    private ComponentRenderer<? extends GuiComponent> componentRenderer;

    public void draw() {
        this.componentRenderer.draw(this);
    }

    //and a setter and getter for the ComponentRenderer
}

And a subclass, wich set a implementation for the componentRenderer:

public class GuiButton extends GuiComponent {
    public GuiButton(/* ... */) {
        //...
        this.setComponentRenderer(new FlatButtonRenderer());
    }

where FlatButtonRenderer is implemented as:

public class FlatButtonRenderer implements ComponentRenderer<GuiButton> {

    @Override
    public void draw(final GuiButton component) {
        //...
    }
}

I can't see where I got something wrong, but the call componentRenderer.draw(this) in GuiComponent does not work with the following error:

enter image description here

As far as I understand this, it says me, that I can't use GuiComponent because it does not derive from GuiComponent, what makes no sense. I've also tried ? super GuiComponent, which will accept the draw() call, but then does not accept the implementation of FlatButtonRenderer

I do not understand this syntax error, does anyone have an idea, how I need to change the code?

EDIT: When I use my IDE's code completion on the call of draw(), it says me, that draw accept one argument of type "null", so for some reason, it is not able to figure out, wich type the argument should be...

Cydhra
  • 581
  • 1
  • 7
  • 17
  • Unrelated to your issue: Why are you trying to pull the drawing into a different object? Is not the purpose of a GuiComponent to be drawn on the screen? – cyroxis Jun 07 '16 at 15:11
  • Because I want that the Gui Button contains the button logic, which is always the same, but I want, that the button can have different layouts and styles. So the renderer can just be switched and I got a working button with different layout. – Cydhra Jun 07 '16 at 15:15
  • Fair enough. I don't know the details of your use case but from what I have seen defining styles or subclassing makes the components easier to use. – cyroxis Jun 07 '16 at 16:29

2 Answers2

2

The problem is that ? extends GuiComponent means "one specific subtype of GuiComponent, but unknown which".

The compiler does not know that this is of the right GuiComponent subtype for the ComponentRenderer. It could be that the renderer only can work with some other specific subclass.

You have to use some kind of self-type pattern to do this in a type-safe way. That way you kind of "connect" the type variable of the renderer with the type of the GuiComponent subclass.

Example:

class Gui {}

interface ComponentRenderer<T extends GuiComponent<T>> {
    public void draw(T component);
}

// T is the self-type. Subclasses will set it to their own type. In this way this class
// can refer to the type of its subclasses.
abstract class GuiComponent<T extends GuiComponent<T>> extends Gui {
    private ComponentRenderer<T> componentRenderer;

    public void draw() {
        this.componentRenderer.draw(thisSub());
    }

    public void setComponentRenderer(ComponentRenderer<T> r) {}

    // This method is needed for the superclass to be able to use 'this'
    // with a subclass type. Sub-classes must override it to return 'this'
    public abstract T thisSub();

    //and a setter and getter for the ComponentRenderer
}

// Here the self-type parameter is set
class GuiButton extends GuiComponent<GuiButton> {
    public GuiButton(/* ... */) {
        //...
        this.setComponentRenderer(new FlatButtonRenderer());
    }

    class FlatButtonRenderer implements ComponentRenderer<GuiButton> {
        @Override
        public void draw(final GuiButton component) {
            //...
        }
    }

    @Override
    public GuiButton thisSub() {
        return this;
    }
}

This is originally (I think) called the curiously recurring template pattern. This answer explains it more.

Community
  • 1
  • 1
Lii
  • 11,553
  • 8
  • 64
  • 88
  • This looks very unhealthy and not very logically (I mean: class GuiComponent>; how does this even make sense?) But it works. Thank you :D But don't mind me the obligatory comment: Java Generics are not made for more than generic lists. – Cydhra Jun 07 '16 at 15:53
  • @Cydhra: It looks really weird when you see it the first time, but when you get it actually makes a lot of sense. `T` is just the type of the current subclass of an superclass. I'll add some links with more information about this to the answer. – Lii Jun 07 '16 at 16:01
  • @Cydhra: But it's pretty messy to have to write out all those self-types all the time. Maybe a better solution in your case is to just ditch type-safety, remove the type parameter from `ComponentRenderer`, just cast the argument to the right subcomponent inside `ComponentRenderer.draw`, and manually make sure the right kind of component is sent to the right kind of renderer. – Lii Jun 07 '16 at 16:13
  • Mh no. I hate TypeCasting, your solution may seem a little confusing, but TypeCasting is just bad code style. The only TypeCasting, you should do, is when you override equals() – Cydhra Jun 07 '16 at 18:34
  • @Cydhra: Your question made me remember [this old answer](https://stackoverflow.com/questions/25458729/this-convoluted-generics-pattern-crashes-eclipse-can-i-make-it-work/33521299#33521299). If your into using advanced generics to gain type safety then you might find it interesting. That person uses the self-type pattern times three! – Lii Jun 08 '16 at 14:21
0

In GuiComponent, change your declaration of componentRenderer to this:

ComponentRenderer<GuiComponent> componentRenderer;
eclipz905
  • 149
  • 1
  • 2
  • 9
  • This does not work, because now, I can no longer set the component renderer to FlatButtonRenderer, because this implementation uses GuiButton as the type and not GuiComponent – Cydhra Jun 07 '16 at 14:10
  • I have this code compiling without error (after creating a mock Gui class). Specifically, what error do you get after making this change? – eclipz905 Jun 07 '16 at 14:28
  • An error at the setComponentRenderer call in GuiButton, that FlatButtonRenderer is not applicable for the method setComponentRenderer(ComponentRenderer) – Cydhra Jun 07 '16 at 14:41
  • How should this work?? ComponentRenderer extends GuiComponent> != ComponentRenderer; so I can't use this setter. The setter only works, when componentRenderer and the setter parameter are of the same type. – Cydhra Jun 07 '16 at 14:52
  • Sorry. that last edit was incorrect advise. I'm looking this over but haven't figured out a solution yet. The issue comes from GuiButton trying to assign a ComponentRenderer using a ComponentRenderer. I'm not sure exactly what you're trying to accomplish, but the circular inheritance structure complicates the issue. Particularly, ComponentRenderer and GuiComponent both referencing each other. It's possible that simplifying this structure could solve some of the problem. – eclipz905 Jun 07 '16 at 15:17
  • Well, I could unbound the ComponentRenderer, then it would no longer refer to GuiComponent, but it does not fix the problem. I see what you mean, but I can't figure out, where this can't work... – Cydhra Jun 07 '16 at 15:22