11

I'm trying to solve a problem and I've come up with this solution (simplified):

package help;

public class Problem {

    private static class A<T> {
        public void foo(T t) {}
    }

    private static class B<T> {}

    private static class C<T> extends A<B<T>> {
        public void foo(T t) {}
    }
}

It wont compile since "foo(T) in help.Problem.C clashes with foo(T) in help.Problem.A; both methods have same erasure, yet neither overrides the other".

I'm not just trying to solve the problem, i also would like to understand what is going on. I noticed that if the B class is omitted, the error is gone.

Also: could you provide an example of a piece of code such, that the compiler wouldn't be able to bind a variable to one of those two methods?

userfault
  • 199
  • 1
  • 10
  • What do you mean by "if class B is omitted, the error is gone"? In your code, class C extends A>. How could that possibly work if you omit class B? Could you paste the code where you omit class B, including class C? – Alexis Dufrenoy May 03 '18 at 15:34
  • @AlexisDufrenoy I meant that class C extends A instead of A> – userfault May 03 '18 at 15:41

2 Answers2

5

The class definition of A means that the compiler "expects" something like this if you want to override foo:

private static class C<T> extends A<B<T>> {
    public void foo(B<T> t) {}
}

However, since you're just providing T as a type to that method rather than B<T>, it's not a valid override of foo in class A - the parameter types must match. This wouldn't cause an error with non-generic types - you've just got an overloaded method (a method that has the same name, but differs in the type / number of its parameters) instead of an overriden one.

However, since the generic types disappear on compilation (through type erasure), this means you can't have two methods differentiated only by an generic type, as their definitions in the bytecode would be identical.

Michael Berry
  • 70,193
  • 21
  • 157
  • 216
1

To see why they erase to the same type, just remove all <> and the stuff inside them, then replace all the generic parameter types with Object:

class Problem {

    private static class A {
        public void foo(Object t) {}
    }

    private static class B {}

    private static class C extends A {
        public void foo(Object t) {}
    }
}

Now you see why.

Not being able to overload a method like this is one of the limitations of generics. Read more here.

The root of the problem is really that in C, T means something different than it does in A. Let's suppose you remove the declaration of foo in C and you have an instance of C<T>. You want to call foo using this instance. What parameter do you need to pass? A B<T>, right? If foo is declared to accept a T in C, that wouldn't override A.foo because A.foo need to accept a B<T>, not just T.

To actually override foo, change the parameter to be of type B<T>.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • I don't want to override foo, I do need to receive a `T` there, I was trying to overload them. your answer and the others helped clarify why it is impossible. thanks! – userfault May 03 '18 at 15:51
  • 2
    @userfault No, you can't overload them. See the link in my answer. – Sweeper May 03 '18 at 15:52