1

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.
Stefan Dollase
  • 4,530
  • 3
  • 27
  • 51

0 Answers0