In the following code example the compiler does not allow me to override the method f
.
public class A {}
public class B extends A {}
public class X {
public void f(Consumer<? super A> consumer) {
consumer.accept(new A());
}
}
public class Y extends X {
@Override // this is line 14
public void f(Consumer<? super B> consumer) { // this is line 15
consumer.accept(new B());
}
}
public static void main(String[] args) {
X x = new X();
Y y = new Y();
X xy = new Y();
Consumer<A> ca = (A a) -> {};
Consumer<B> cb = (B a) -> {};
x.f(ca); // (1) Consumer takes A and B and is called with A.
// x.f(cb); // (2) Consumer only takes B but would be called with A, which is a type error.
y.f(ca); // (3) Consumer takes A and B and is called with B.
y.f(cb); // (4) Consumer only takes B and is called with B.
xy.f(ca); // (5) Consumer takes A and B and is called with B.
// xy.f(cb); // (6) Consumer only takes B and would be called with B, which is not a type error but since xy has the compile type X this is not allowed.
}
It produces the following error messages.
Testing.java:15: error: name clash: f(Consumer<? super B>) in Y and f(Consumer<? super A>) in X have the same erasure, yet neither overrides the other
public void f(Consumer<? super B> consumer) {
^
Testing.java:14: error: method does not override or implement a method from a supertype
@Override
^
2 errors
I mapped out all relevant cases in the bottom. Only the cases (2) and (6) produce compiler errors and these errors are irrelevant for me. The case that I need to work is case (5), where I can provide a Consumer<A>
to a Y
that is stored in an X
.
In my understanding the example should not only work at runtime, but it should also type check at compile time. Let's use the notation A >= B
if A
is a super type of B
.
Because we have A >= B
and ? super
is contravariant, we get Consumer<? super B> >= Consumer<? super A>
.
Since method parameters are also contravariant, we get f(Consumer<? super A>) >= f(Consumer<? super B>)
and thus it should be possible to override f(Consumer<? super A>)
with f(Consumer<? super B>)
.
Is this not possible in Java, or is there some other way to write it down such that the compiler accepts it?
From the marked duplicate I understand that my second step in the argument above is not valid, since in Java method parameters are not contravariant when it comes to overriding. I also found the following workaround.
public class Y extends X {
@Override
public void f(Consumer<? super A> consumer) {
ff(consumer);
}
public void ff(Consumer<? super B> consumer) {
consumer.accept(new B());
}
}
In this way, the calls can be adjusted like this.
x.f(ca); // (1) Consumer takes A and B and is called with A.
// x.f(cb); // (2) Consumer only takes B but would be called with A, which is a type error.
y.ff(ca); // (3) Consumer takes A and B and is called with B.
y.ff(cb); // (4) Consumer only takes B and is called with B.
xy.f(ca); // (5) Consumer takes A and B and is called with B.
// xy.f(cb); // (6) Consumer only takes B and would be called with B, which is not a type error but since xy has the compile type X this is not allowed.