简体   繁体   中英

Type Erasure and Overloading in Java: Why does this work?

I have the following code:

public class Pair< T, U > {
    public T first;
    public U second;
}
public class Test {
    public int method( Pair< Integer, Integer > pair ) {
        return 0;
    }
    public double method( Pair< Double, Double > pair ) {
        return 1.0;
    }
}

This actually compiles and works like one would expect. But if the return types are made to be the same, this doesn't compile, with the expected "name clash: method(Pair) and method(Pair) have the same erasure"

Given that the return type isn't part of the method signature, how is this overloading possible?

Consider the following 4 methods

           Java code                        bytecode

m1:    Byte f(List<Byte> list)           f List -> Byte
m2:    Long f(List<Byte> list)           f List -> Long
m3:    Byte f(List<Long> list)           f List -> Byte
m4:    Long f(List<Long> list)           f List -> Long

According to the current Java Language Spec,

  • m1 and m2 cannot coexist, nor can m3 and m4. because they have the same parameter types.

  • m1 and m3 can coexist, so can m1 and m4. because they have different parameter types.

But javac 6 only allows m1+m4, not m1+m3. That's related to the bytecode representation of methods, which includes return types. Therefore, m1+m4 are ok, but not m1+m3.

This is a screwup where Java and JVM specs don't see eye to eye. There is no "correct" way for javac.

While it sucks, the good news is, overloading is a vanity, not a necessity. We can always use different, more descriptive and distinct names for these methods.

Overloading is done at compile-time.

Although the generic parameters are erased at run-time, they're still available to the compiler to resolve overloads.

A Java method signature actually does include the return type; if you've ever worked with JNI you've seen type descriptors like (LPair;)D and (LPair;)I . That last character denotes the return types of your two methods. Although the Java language rule is that the parameters must differ for two overloaded methods, the class file format can actually distinguish methods based only on their return types. When there is generic type information to allow the compiler to resolve the overloads based on their arguments, then as long as the return types are different the erasures will have different signatures, and it all works fine. If the returns types are the same, though, then after erasure the methods have the same signature, the class file can't tell the difference, and you have a problem.

I believe that you are actually looking at typing wrong. By saying that the first method takes Pair you are giving it a very specific type. It's like saying method(String, String). The second method of Pair is like saying method(Person, Person). This too is very specific at a typing level. If you would change your methods to be method(Pair <T,U> , Pair <T,U> ) and have that twice, you'd break the compile.

So short answer: since you've strongly typed Pair to mean two different things, Generics aren't at play, just typing rule.

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