Search code examples
javagenericsoverridingupperbound

Overriding multiple generic functions with a single function with common upper bounds


I have two different interfaces

interface ColumnSet {
   <V, I extends Column & Input<V>> V getValue(I column);
}

interface ParameterSet {
   <V, I extends Parameter & Input<V>> V getValue(I value);
}

where the types Column and Parameter are simply marker interfaces to stop Columns being used where Parameters should be and vice-versa. Behind the scenes I would therefore like to have a single class that implements them both as follows:

class ObjectSet implements ColumnSet, ParameterSet {
   @Override public <V, I extends Input<V>> V getValue(I input) {
       ...
   }
}

Logically it seems like ObjectSet.getValue should be a valid override for both ColumnSet.getValue and ParameterSet.getValue as it takes any Input<V> as an argument which is upper bounded by both Column & Input<V> and Parameter & Input<V>. However Java 9 doesn't recognise it as overriding either of them reporting The method getValue() of type ObjectSet must override or implement a generic supertype method.

Is this a limitation of generics in Java or am I missing something fundamental?

(Obviously I can't create two separate methods in ObjectSet due to them having the same erasure, which leaves me with the alternative of giving different names for the two getValue methods in the interfaces which I'm trying to avoid).


Solution

  • According to (§8.4.8.1), an instance method m1 overrides another instance method m2 if the signature of m1 is a subsignature (§8.4.2) of the signature of m2.

    In other words, the overriding method signature should be the same as either overridden method signature or erasure (§4.6) of the overridden method signature.

    So overridden method parameters can't be replaced with parameters of a less specific type in the overriding method. You can read why this is so here.


    In your case, the ObjectSet#getValue signature isn't the same as the ColumnSet#getValue signature and its erasure signature (Object getValue(Column column)). The same goes for ParameterSet#getValue.


    As it was pointed out by @samabcde if base methods are declared like this:

    <V, I extends Column & Input<V>> V getValue(I column);
    <V, I extends Parameter & Input<V>> V getValue(I value);
    

    You can implement them as follows:

    @Override
    public <V, I extends Column & Input<V>> V getValue(I value) {
        return doGetValue(value);
    }
    
    @Override
    public <V, I extends Parameter & Input<V>> V getValue(I value) {
        return doGetValue(value);
    }
    
    private <V, I extends Input<V>> V doGetValue(I value) { ... }
    

    And if base methods are declared like this:

    <V, I extends Input<V> & Column> V getValue(I value);
    <V, I extends Input<V> & Parameter> V getValue(I value);
    

    You can only implement their erasure as follows:

    @Override
    public Object getValue(Input value) { ... }
    

    Since neither of these options looks good I recommend redesigning your code the following way:

    public interface Input<R, V> {
        // ...
    }
    
    public interface ObjectSet<R> {
        <V> V getValue(Input<R, V> input);
    }
    
    public class ObjectSetImpl<R> implements ObjectSet<R> {
        @Override
        public <V> V getValue(Input<R, V> input) {
            // ...
        }
    }
    

    Now you can easily create ObjectSet instances that only accept Input with specific type parameter R:

    public interface Column<V> extends Input<Column<?>, V> {}
    
    ObjectSet<Column<?>> columnSet = new ObjectSetImpl<>();
    

    If you dislike writing ObjectSet<Column<?>>, you can create a better-named implementation of ObjectSet<Column<?>> that delegates all the work to ObjectSetImpl:

    public class ColumnSet extends DelegatingObjectSet<Column<?>> {}
    
    ColumnSet columnSet = new ColumnSet();
    

    Where DelegatingObjectSet is:

    abstract class DelegatingObjectSet<R> implements ObjectSet<R> {
        // you can use dependency injection here
        private final ObjectSet<R> delegate = new ObjectSetImpl<>();
    
        @Override
        public <V> V getValue(Input<R, V> input) {
            return delegate.getValue(input);
        }
    }