Method Has the Same Erasure as Another Method in Type

Method has the same erasure as another method in type

This rule is intended to avoid conflicts in legacy code that still uses raw types.

Here's an illustration of why this was not allowed, drawn from the JLS. Suppose, before generics were introduced to Java, I wrote some code like this:

class CollectionConverter {
List toList(Collection c) {...}
}

You extend my class, like this:

class Overrider extends CollectionConverter{
List toList(Collection c) {...}
}

After the introduction of generics, I decided to update my library.

class CollectionConverter {
<T> List<T> toList(Collection<T> c) {...}
}

You aren't ready to make any updates, so you leave your Overrider class alone. In order to correctly override the toList() method, the language designers decided that a raw type was "override-equivalent" to any generified type. This means that although your method signature is no longer formally equal to my superclass' signature, your method still overrides.

Now, time passes and you decide you are ready to update your class. But you screw up a little, and instead of editing the existing, raw toList() method, you add a new method like this:

class Overrider extends CollectionConverter {
@Override
List toList(Collection c) {...}
@Override
<T> List<T> toList(Collection<T> c) {...}
}

Because of the override equivalence of raw types, both methods are in a valid form to override the toList(Collection<T>) method. But of course, the compiler needs to resolve a single method. To eliminate this ambiguity, classes are not allowed to have multiple methods that are override-equivalent—that is, multiple methods with the same parameter types after erasure.

The key is that this is a language rule designed to maintain compatibility with old code using raw types. It is not a limitation required by the erasure of type parameters; because method resolution occurs at compile-time, adding generic types to the method identifier would have been sufficient.

Overloading a method: both methods have same erasure

What should I do to achieve method overloading, without raising an error?

Simple: don't try to overload the method with parameters with the same erasure.

A few options:

  1. Just give the methods different names (i.e. don't try to use overloading)
  2. Add further parameters to one of the overloads, to allow disambiguation (ideally only do this if you actually need those parameters; but there are examples in the Java API where there are junk parameters simply to avoid overloading issues).
  3. Bound the type variable, as suggested by @Kayaman:

    <V extends SomethingOtherThanObject>

Erasure of method is the same as another method in type

Your problem is due to type erasure: the parametrized type information in your Lists is erased at runtime, hence the methods have a virtually identical signature and your code cannot compile.

In order to solve your problem, here's a solution that generalizes the common features of Developer and Student, thus only requiring one getArrayOfTimespan method for both given parametrized types:

// common interface to Student and Developer classes
interface Datable {
LocalDate getStartDate();
}
// dev class implementing "datable"
class Developer implements Datable {
@Override
public LocalDate getStartDate() {
// TODO Auto-generated method stub
return null;
}
}
// student class implementing "datable"
class Student implements Datable {
@Override
public LocalDate getStartDate() {
// TODO Auto-generated method stub
return null;
}
}
// parameter interpreted as list of super type to both dev and student
private LocalDate[][] getArrayOfTimespan(List<Datable> args)
{
for (Datable d: args) {
// TODO something
LocalDate foo = d.getStartDate();
}
// TODO return something
return null;
}

Erasure of method is the same as another method

Let's take a look at your methods and their erasure:

  1. <T extends Number> void method1(T t)

    would have the erasure

    void method1(Number t)

    because every T is Number or one of it's subclasses.

  2. <T extends Number, U extends List<String>> U method1(T t)

    has the erasure

    List method1(Number t)

    Again, because every T is Number or it's subclass.

  3. <T> void method1(T t)

    has the erasure

    void method1(Object t)

    because T can be any class.

As you can see, the erasures (without the return type) for 1. and 2. are equal.

3. Is different, so it is allowed as overload.

Java name clash error, a method has the same erasure as another method

As per Java Documentation,

If a subclass defines a static method with the same signature as a static method in the superclass, then the method in the subclass hides the one in the superclass.

The distinction between hiding a static method and overriding an instance method has important implications:

  • The version of the overridden instance method that gets invoked is the one in the subclass.
  • The version of the hidden static method that gets invoked depends on whether it is invoked from the superclass or the subclass

Method has (unexpectedly) the same erasure as another method

You've recognized that the issue is about type-erasure,
and that you can't "overload" on the one parameter with type-erasure in the way
(type-erasure - you will be assimulated).
But, you say,

The generic type signatures are distinct, why did you get a conflict?

The two types T extends Aclass&Binterface and Aclass are distinct, aren't they?

Well, yes and no. While in truth they are distinct, under type-erasure they're not distinct**1. An excellent resource explains: "In the process of type erasure the compiler replaces type parameters by their leftmost bound". Which in your case was Aclass, which led to the conflict between the two methods.

The explanation suggests a ...

Solution: formalize the combound**2 generic type declaration to establish a unique leftmost bound

Replace <T extends Aclass&Binterface> with <T extends AandB>, where AandB is created thus:

  1. Define interface Ainterface with Aclass method signatures

  2. Have Aclass implement Ainterface

  3. Define interface AandB extends Ainterface, Binterface.

OR

you can use instanceof.


**1 "Because the longer wave lengths are refracted by the particles in the atmosphere", per Duncan Jones, another excellent resource.

**2 That'd be compound, cause I couldn't resist the fun :-/

Java generics both methods have same erasure error

In order for

public Account search(List<SearchCriteria> t) { ...}

to override

T search(List<?> t);

The arguments must be the same after type parameter substitution, but ? is not SearchCriteria.

Therefore, if you want to keep these methods (the inheritance looks a bit wild to me), you'll need to parameterise the types further.

public interface GenericService<T, C> {
// ...
T search(List<C> t); // probably change that parameter name
}

public abstract class AbstractService<T, C>
implements GenericService<T, C>
{
}

public class AccountService
extends AbstractService<Account, SearchCriteria>
implements GenericService<Account, SearchCriteria> // unnecessary
{
// ...
@Override
public Account search(List<SearchCriteria> t) { /* ... */ }
}

Both methods have same erasure yet neither overrides the other

From JLS 8.4.2:

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 type parameters of M, the same formal parameter types.

How is that relevant to your code?

  1. The first method signature you posted is:

    public void func(String str, List<String> lst)

    This signature exists like this only at compile-time because it uses generics. Once compiled, the runtime signature will be like below (List<String> becomes List) due to type erasure:

    public void func(String str, List lst)
  2. The second method signature in your example is:

    public void func(String str, List<Integer> lst)

    While the starting point is different – a List of Integer instead of a List of String – the runtime signature for this sees a similar change, replacing List<Integer> with List:

    public void func(String str, List lst)

Even though your code specifies differences in the argument list (List<String> vs List<Integer>) which leads you to believe there are two different method signature, the two signatures are effectively the same:

  • they are both named "func", and
  • they have the same argument list: String, List

In Erasure of Generic Types, they show an example similar to what you're seeing where a type of Node<T> becomes Node. That is happening with your examples, too, where List<String> becomes List.

During the type erasure process, the Java compiler erases all type parameters and replaces each with its first bound if the type parameter is bounded, or Object if the type parameter is unbounded.

Note that the return type is not part of a method signature uniqueness, which is why changing one to return Object didn't make a difference.

Here's a bit more info from the Java Tutorial section on Type Erasure, with a little emphasis added in a few spots:

Generics were introduced to the Java language to provide tighter type checks at compile time and to support generic programming. To implement generics, the Java compiler applies type erasure to:

  • Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
  • Insert type casts if necessary to preserve type safety.
  • Generate bridge methods to preserve polymorphism in extended generic types.
    Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.


Related Topics



Leave a reply



Submit