Dragon
Dragon

Reputation: 303

Why can't I add a type parameter to an overriding method?

I need to implement a method from a class Foo, in my subclass Bar:

class Foo {
    public abstract void foo();
}

class Bar extends Foo {
    
    private <T> Map<T, List<T>> getStuff() { ... }

    @Override
    public void foo() {
        Map<?, List<?>> stuff = getStuff();
        for (Entry<?, List<?>> e : stuff.entrySet()) {
            Object key = e.getKey();
            List<?> lst= e.getValue();
            lst.add(key);  // illegal: need ?, got Object
        }
    }
}

As you can see, my method would benefit from a type parameter:

@Override
public <T> void foo() {
    Map<T, List<T>> stuff = getStuff();
    for (Entry<T, List<T>> e : stuff.entrySet()) {
        T key = e.getKey();
        List<T> lst= e.getValue();
        lst.add(key);  // legal!
    }
}

(I could also use raw types because I know it'll work, but everything I've read says you really shouldn't do that)

Unfortunately, this is illegal - my method foo in Bar "clashes with foo() in Foo; both methods have the same erasure, yet neither overrides the other".

Which seems odd, given that my type parameter does not alter the parameters or return type of foo - any place it is okay to use Foo.foo in, it is also okay to use my (illegal) Bar.foo in.

My ugly hack of a workaround has, up until this point, been to just write a whole new (parameterized) method:

public void foo() {
    realFoo();
}

private <T> void realFoo() {
    // parameterized version of .foo() above
}

I have two questions:

  1. Why does adding a type parameter to a method prevent it from overriding an unparameterized method in its superclass?
  2. Is there a better way to get the behavior I want?

Upvotes: 0

Views: 1019

Answers (2)

Chien Hao Tan
Chien Hao Tan

Reputation: 1

public class Test {
    static class Parent {
        public <T> Object foo() { System.out.println("Parent"); return null; };
    }

    static class Child extends Parent {
        @Override
        public Object foo() { System.out.println("Child"); return null; };
    }

    public static void main(String[] args) {
        Parent p = new Child();
        p.foo(); // output: Child
    }
}

the above answer is great, just wanted to add a small addendum that technically, one can remove type parameters in the overriding method. i don't have enough reputation to comment so i'm writing here

Upvotes: 0

Andy Turner
Andy Turner

Reputation: 140534

See JLS 8.4.2:

8.4.2. Method Signature

Two methods or constructors, M and N, have the same signature if they have the same name, the same type parameters (if any) (§8.4.4), and, after adapting the formal parameter types of N to the the type parameters of M, the same formal parameter types.

The signature of a method m1 is a subsignature of the signature of a method m2 if either:

  • m2 has the same signature as m1, or
  • the signature of m1 is the same as the erasure (§4.6) of the signature of m2.

Two method signatures m1 and m2 are override-equivalent iff either m1 is a subsignature of m2 or m2 is a subsignature of m1.

Because type parameters are part of the method signature, you can't add or remove type parameters in overriding methods.

My ugly hack of a workaround...

Yes, it's ugly, but this is the right way to do it.

Upvotes: 1

Related Questions