0

Here's the code I essentially want, which won't compile:

interface Interface {
  interface ArgumentInterface {
    // Some methods
  }

  void doCallback(Consumer<? super ArgumentInterface> callback);
}

interface SubInterface extends Interface {
  interface ArgumentSubInterface extends ArgumentInterface {
    // Some more methods
  }

  @Override
  void doCallback(Consumer<? super ArgumentSubInterface> callback);
}

The idea here is that Interface will pass an instance of ArgumentInterface to the Consumer that the user provides, while SubInterface will pass an instance of the more specific ArgumentSubInterface. In particular, I want the user to be able to pass a Consumer<ArgumentSubInterface> to SubInterface.doCallback() and have that work.

Naively, it seems like this should work as-written: Any argument that is accepted by Interface's version will also be accepted by SubInterface's version. However, Java claims that the method doesn't override.

D0SBoots
  • 705
  • 6
  • 18

3 Answers3

6

It doesn't work because Java doesn't allow overrides with contravariant parameters. What you can do is parameterize Interface with the specific type of ArgumentInterface you want to accept:

interface Interface<T extends ArgumentInterface> {
  interface ArgumentInterface {
    // Some methods
  }

  void doCallback(Consumer<? super T> callback);
}

interface SubInterface extends Interface<ArgumentSubInterface> {
  interface ArgumentSubInterface extends ArgumentInterface {
    // Some more methods
  }

  // This is implicitly inherited
  // @Override
  // void doCallback(Consumer<? super ArgumentSubInterface> callback);
}
Community
  • 1
  • 1
shmosel
  • 49,289
  • 6
  • 73
  • 138
  • This doesn't work. The reason SubInterface extends Interface is so that you can pass a SubInterface where an Interface is declared. In your example, you can't pass a SubInterface where an Interface is declared. Or, put another way: How would this extend to a chain of three interfaces? – D0SBoots Sep 22 '16 at 17:46
  • @D0SBoots That's because `SubInterface` is not a subclass of `Interface` ([see here for details](http://stackoverflow.com/q/2745265/1553851)). But it is a subclass of `Interface extends ArgumentInterface>`. – shmosel Sep 22 '16 at 17:55
  • Yes, I get that SubInterface is not a subtype of Interface, that's the problem. :) Maybe focusing on my second point is more illustrative: Suppose we extend the problem to Interface, SubInterface and SubSubInterface, with respective ArgumentInterfaces in the natural way. I don't think what you're proposing can generalize to that, and the reasons it can't are the same as why the two-interface version doesn't work for my real (non-example) issue. – D0SBoots Sep 22 '16 at 18:17
  • @D0SBoots What's the problem with `Interface extends ArgumentInterface>`? – shmosel Sep 22 '16 at 18:20
  • That would render `SubInterface` obsolete (unless it has more members not specified here)… – Holger Sep 22 '16 at 18:50
  • 1
    @Holger Well that's pretty much the point of generics, right? I left the subclass in the answer because it was in the question and I didn't want to make any assumptions. – shmosel Sep 22 '16 at 18:55
  • @shmosel So, in my real-world issue both Interface and SubInterface have other methods, which I omitted here for brevity. Let's look at the 3-deep chain: If you start with Interface extends ArgumentInterface>, and then have SubInterface extends Interface, what does SubSubInterface extend? It can't extend both SubInterface and Interface. – D0SBoots Sep 23 '16 at 01:48
  • @D0SBoots You can do `SubInterface extends Interface` and then `SubSubInterface extends SubInterface`. – shmosel Sep 23 '16 at 02:18
1

You said “Any argument that is accepted by Interface's version will also be accepted by SubInterface's version”. This is correct, but not how method overriding works. It’s like if you had written:

interface Interface {
    void method(String s);
}
interface SubInterface extends Interface {
    void method(Object o);
}

while method(Object) is capable of accepting anything that method(String) can, it still is not overriding that method. In this simplified example, you could solve the issue as

interface SubInterface extends Interface {
    @Override default void method(String s) {
        method((Object)s);
    }
    void method(Object o);
}

which has the desired effect of SubInterface implementors having to implement one method with a more abstract type.

However, when the parameter types in question are Consumer<? super ArgumentInterface> and Consumer<? super ArgumentSubInterface>, we can’t provide overloaded methods, as after type erasure both methods have the parameter type Consumer, which is not allowed. So the only work-around, is to use distinctively named methods rather than overloads:

interface Interface {
    interface ArgumentInterface {
      // Some methods
    }
    void doCallback(Consumer<? super ArgumentInterface> callback);
}

interface SubInterface extends Interface {
    interface ArgumentSubInterface extends ArgumentInterface {
      // Some more methods
    }
    @Override
    default void doCallback(Consumer<? super ArgumentInterface> callback) {
        doCallbackSub(callback);
    }
    void doCallbackSub(Consumer<? super ArgumentSubInterface> callback);
}

Not the best imaginable, I guess, but the best you can get with Java Generics…

Holger
  • 285,553
  • 42
  • 434
  • 765
0
interface ArgumentInterface {
  // Some methods
}

interface Interface<IFace extends ArgumentInterface> {

  void doCallback(Consumer<IFace> callback);
}

interface ArgumentSubInterface extends ArgumentInterface {
  // Some more methods
}

interface SubInterface<IFace extends ArgumentSubInterface>
extends Interface<IFace> {

// No need to override anymore, the selection is applied at the 
// SubInterface generics' arguments
//  @Override
//  void doCallback(Consumer<? super ArgumentSubInterface> callback);
}
Adrian Colomitchi
  • 3,974
  • 1
  • 14
  • 23