0

This is yet another question about methods with signatures using parametrized types in Java.

Say you have the following two methods:

    static void f(List<Integer> l) {}
    static void f(List<String> l) {}

The compiler will complain that both methods have same signature after type erasure (both argument types are erased to just List).

Many similar questions on stackoverflow ask why it is so, but always the question is about instance (non static) methods (see Method has the same erasure as another method in type for instance).

Usually half the answers are based on the following (very wrong) argument: the compiler would erase all type parameters in the bytecode and make methods undistinguishable. Ok then, just print the bytecode with javap and you will see whether everything is erased! (although the bytecode loses a lot of parametrization data, full method signatures are actually kept, which is definitely useful when you want to compile a new class using a dependency containing generic classes and methods).

On the other hand, the best answers usually quote JLS 8.4.2 and explain that, for compatibility with old, pre-generic, Java versions (and raw types in newer versions), methods with override-equivalent signatures are forbidden.

I am ok with the latter argument, except it only means something for instance methods (non static), as static methods cannot be overridden anyway.

Probably there is a similar explanation for static methods, but I fail to pinpoint it. Could somebody help me understand this?

ADegorre
  • 41
  • 5
  • There is nothing 'very wrong' about answers which state that the compiler performs type erasure. This is merely a well-documented fact, and `static` versus instance methods has nothing to do with it; nor does method signature information which properly speaking isn't part of the bytecode at all. – user207421 Oct 15 '19 at 12:18
  • @user207421 I think you misread me. I do not doubt type erasure. For instance, it is blatant that you cannot get **any** information about type parameters **at run time**. I am just saying that many people misunderstand type erasure when they say the bytecode contains nothing about generics. This is just wrong. By the way, have you tried running the command `javap` on a generic class? Maybe you would be surprised... – ADegorre Oct 15 '19 at 12:32
  • Ah, by the way, from the JVM specification: https://docs.oracle.com/javase/specs/jvms/se13/html/jvms-4.html#jvms-4.7.9 – ADegorre Oct 15 '19 at 12:36

1 Answers1

2

The argument for backwards-compatibility still holds.

If you had this code (not using generics, strongly discouraged, but legal even today, and perfectly normal in Java 1.4 code that is supposed to still compile), which of your two methods should the compiler choose?

List rawList = new ArrayList();
YourClass.f(rawList);

More to the point, assuming you somehow choose one of the two, in the resulting call-site bytecode, the generics are still erased, so at runtime, the JVM does not know which of the two f(List) you meant. Method calls specify the method name and signature, but that signature does not include generics. That is does not is due to compatibility concerns. Could they have tried to push harder on this with something like a new opcode with an extended call specification? Maybe. But that is how it is now.

On the other hand, the best answers usually quote JLS 8.4.2 and explain that, for compatibility with old, pre-generic, Java versions (and raw types in newer versions), methods with override-equivalent signatures are forbidden.

I am ok with the latter argument, except it only means something for instance methods (non static), as static methods cannot be overridden anyway.

Well, you cannot override static methods, but your two methods are still "override-equivalent", meaning they have a signature that is so close to each other that you can have only one of them at a time (in a subclass situation, one would override the other if inherited because of that --- but it also means that you cannot have two of such methods on the same class).

Note that this does not pose any real issues, as you can always avoid the need for "overloading" by just changing to distinct method names.

Community
  • 1
  • 1
Thilo
  • 257,207
  • 101
  • 511
  • 656
  • I can see how ambiguity would be embarrassing. But this is pretty much the same issue as with overloading. Say you have following methods: `static void f(Number x, Object y) {}` and `static void f(Object x, String y) {}` in the same class `C`. This class would compile fines although the call `C.f(new Object(), new Object())` would be ambiguous. For some reason, the team behind java decided that only this would not compile, not the definition of potentially conflicting overloads. I just wonder why a different approach was chosen with respect to generic methods. – ADegorre Oct 15 '19 at 12:08
  • The difference is that the overloading error will be resolved at compile time. In the generate bytecode, it will say with overload to choose. Whereas the generated bytecode for the call to `f` will not include the generic types, it will just link to an `f` that takes a `List`. – Thilo Oct 15 '19 at 12:14
  • My example was about static methods only. If the compiler would accept the two methods with signatures similar up to parametrization, in the bytecode they would just be 2 different methods, with different addresses. The compiler would have no trouble dealing with this. By the way, call sites in the bytecode (invokestatic instructions) just contain a method index, not the signature, so there is really nothing particular here (I mean, it is exactly as if the two methods had different names). – ADegorre Oct 15 '19 at 12:23
  • That method index will have been resolved by the signature. It cannot be compiled in. How else would you deal with class evolution (if you compile against an old version of the jar, your code will still run against a new version, even if a lot of methods have been re-arranged, as long as the one that matches your signature is still present). – Thilo Oct 15 '19 at 12:27
  • Anyway, the current mechanism has generic types erased at runtime, so it cannot support overloading by generics only. Could they have done this differently? Probably, but they did not. Have they thought about it? Most certainly. It is always a tradeoff, and preserving backwards-compatibility even in weird edge cases is Java's strong suit. – Thilo Oct 15 '19 at 12:37
  • 1
    In the JVM spec you linked to, you will see that the call-site method descriptor (which is used to dynamically resolve to the method being called) only stores the classnames of the parameters, not any generics: https://docs.oracle.com/javase/specs/jvms/se13/html/jvms-4.html#jvms-FieldType – Thilo Oct 15 '19 at 12:44
  • Ok, the argument of invokestatic is actually an index in the constant pool of the calling class. And the content of the constant pool has no generic data concerning method signatures. I guess this proves that allowing the definition of the two methods is not possible unless the constant pool format is updated. This, in turn, could cause compatibility issues (but would it, really? I mean, older JVMs are not supposed to handle bytecode meant for newer JVMs... ). – ADegorre Oct 15 '19 at 12:51
  • Answering to your last comment: yes descriptors are typed-erased, but not signatures (these are two different things). But it happens signatures are not used in the constant pool. – ADegorre Oct 15 '19 at 12:52
  • (besides, I cannot upvote your answer, but I guess your answer + the discussion which follows decently address my question, thank you for your time!) – ADegorre Oct 15 '19 at 13:14