Search code examples
javagenericsbounded-wildcardgeneric-method

Implementing an Upper Bounded Generic Method


I am having trouble building a somewhat circular generic configuration. I need a "state" interface that provides a getter to a generic "container" interface. The container interface on its methods needs to accept a generic "state" as one of the parameters. I have tried various options (truly circular generic class parameters, various variations of below), but what I have below is the closest to what I think I need:

interface Container<K> {
    <C extends Container<K>,S extends State<K,C>> C setData(K key, Object val,S state);
}

interface State<K,C extends Container<K>>{
    C getContainer();
}

class BasicContainer<K> implements Container<K> {
    public <C extends BasicContainer<K>, S extends State<K,C>> C setData(K key, Object val, S state) { return this;}
}

class BasicState<K> implements State<K,BasicContainer<K>> {
    BasicContainer<K> container = new BasicContainer<K>();
    public BasicContainer<K> getContainer(){
        return container;
    }
}

Alas, the compiler is giving the famous methods have same erasure yet neither overrides the other for the BasicContainer method. I believe this is because while C extends BasicContainer<K> is a subtype of C extends Container<K>, S extends State<K,C> is not a subtype of S extends State<K,C> in Container<K>.

Any suggestions on how to accomplish by desired configuration?

Update I will need other implementations of Container that also need to work with a State implementation, but the State implementation will not return those implementations. This is where the circular parameters on a class failed.


Solution

  • It looks like C ought to be a type parameter on the Container interface, like K.

    In general, if Foo is a supertype of Bar, Bar cannot add additional constraints to generic type parameters on methods from Foo. This just follows from the Liskov substitution principle: any Bar must be able to be used for anything a Foo can be used for.

    Constraining C extends BasicContainer<K> in the type parameters of BasicContainer.setData, when Container.setData only has the constraint C extends Container<K>, specifically violates this rule.

    What you can do instead is have the upper bound on C as a type parameter on Container, and set it to a different value in BasicContainer.

    This could look like one of the following:

    interface Container<K, C extends Container<K, C>> {
        <S extends State<K,C>> C setData(K key, Object val,S state);
    }
    class BasicContainer<K> implements Container<K, BasicContainer<K>> { ... }
    

    ...or, if you find you need the additional generics,

    interface Container<K, C extends Container<K, C>> {
        <C2 extends C, S extends State<K,C>> C2 setData(K key, Object val,S state);
    }
    class BasicContainer<K> implements Container<K, BasicContainer<K>> { ... }
    

    (though bluntly, the second version is almost certainly not what you want).