简体   繁体   中英

error in overriding generic collections in Java

When I try to override a method that takes a List<String> , I get the following compile error .

Multiple markers at this line:
- The method getname(List<Integer>) of type child must override or implement a supertype method
- Name clash: The method getname(List<Integer>) of type child has the same erasure as getname(List<String>) of type parent but does not override it

I was under the impression, that due to erasure , a method that takes a List<String> and a subclass method that takes a List<Integer> would be considered as overridden , because the signature of both methods are same after erasure.

Here is the definition for method signature that involves erasure .
I do not understand why this error comes and what exactly it means.

My code is below:

class parent {

     public void getname(List<String> num) {
        System.out.printf("parent class: %s",num);
     }

}

class child extends parent {

    @Override  // here is the compile error
    public void getname(List<Integer> num) {
        System.out.printf("child class: %s",num);
    }
}

List<String> and List<Integer> are different types and getname(List<String> num) and getname(List<Integer> num) are methods with different signatures. So the second doesn't override the first. So child can not extends parent whit this method.

The error message is pretty clear: it has the same erasure, but the types don't match, so it's not considered an override. A List<Integer> is not a List<String> ; it can't treat it as either an override (which would require the types to match exactly) nor an overload (which would require the erasures to be different).

Basically your impression is incorrect and this is impossible. The JLS considers this specifically illegal.

From 8.4.2 :

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.

The emboldened bit is important because it doesn't say "the erasure of m1 is the same as the erasure of m2". What it actually does allow is this (and some more convoluted examples like it):

class A {
    void process(List<String> list) {}
}
class B extends A {
    @Override
    void process(List list) {} // |List| is erasure of List<T>
}

Since the method signature of B.process is the erasure of A.process it is an override.

According to 8.4.9 , an example like in the OP could then be an overload because the signatures are not override-equivalent:

If two methods of a class ... have the same name but signatures that are not override-equivalent, then the method name is said to be overloaded.

Except that it's specifically a compile-time error ( 8.4.8.3 ):

It is a compile-time error if a type declaration T has a member method m1 and there exists a method m2 declared in T or a supertype of T such that all of the following conditions hold:

  • m1 and m2 have the same name.

  • m2 is accessible from T.

  • The signature of m1 is not a subsignature (§8.4.2) of the signature of m2.

  • The signature of m1 or some method m1 overrides (directly or indirectly) has the same erasure as the signature of m2 or some method m2 overrides (directly or indirectly).

These restrictions are necessary because generics are implemented via erasure. The rule above implies that methods declared in the same class with the same name must have different erasures. ...

To add to the answers already here, I want to comment about the signature of the methods is the same after erasure ... but the compiler checks the method type before erasure.

You could do something like creating a "different" Parent class, such as

class Parent {
  public void getname(List<Integer> num) {
    System.out.printf("child class: %s",num);
  }
}

, compile it, use it to compile Child class, and then mix your original Parent.class and Child.class in the same JVM without issue, avoiding the compiler checks and using type erasure.

But as long as the compiler notices doing something like that "in the same run", it will fail by the reasons explained by Ashot and Louis.

The error you have pointed out is obviously because of the @Override annotation. When validating annotations (which I presume must happen at the very early stage of compiling process) the type erasure hasn't occurred. So the types List is not same as List and you get the warning. But even without that annotation I get an error

    name clash: getname(java.util.List<java.lang.Integer>) in child and getname(java.util.List<java.lang.String>) in parent have the    same erasure, yet neither overrides the other
class child extends parent{`. 

This error clearly says that they have the same erasure but still neither over rides. So Java in principle doesn't allow it. I can understand it will lead to many problems/confusions if allowed. For eg if someone makes a call new Child().get_name( new List<String> () ) , it can't be resolved to the Child method which will break the concept of over riding. So it is correct that it is not allowed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM