Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explicit method type parameter ignored on a raw class type; compiler bug? [duplicate]

I am getting a compiler error calling a generic method with explicit type parameters, as if the explicit type parameter had not been taken into account. Minimal example:

class CastExample {
    static class ThingProducer<S> {
        public <T> T getThing() { return null; }
    }

    static class ThingA {}

    public static void main(String... args) {
        ThingProducer thingProducer = new ThingProducer();
        ThingA thingA = thingProducer.<ThingA>getThing(); // compile error here
    }
}

ThingProducer is a raw type since the class has a type parameter, but in calling getThing we are not referencing the class type parameter, but instead providing the method type parameter. Per my understanding of the JLS, this should be legal, but it gives me this error:

incompatible types: Object cannot be converted to ThingA

The error disappears if I

  • remove the <S> from ThingProducer
  • or make getThing static
  • declare thingProducer ThingProducer<?> instead of the raw type ThingProducer

Is this a compiler bug? If not, what rule in the JLS defines this behavior?

like image 257
Andrew Spencer Avatar asked Nov 16 '15 12:11

Andrew Spencer


1 Answers

Section 4.8 of the Java Language Specification answers your question:

The type of a constructor (§8.8), instance method (§8.4, §9.4), or non-static field (§8.3) of a raw type C that is not inherited from its superclasses or superinterfaces is the raw type that corresponds to the erasure of its type in the generic declaration corresponding to C.

In your example, getThing() is an "instance method ... of a raw type C [in this case, ThingProducer] which is not inherited". According to the JLS, its type is "the raw type that corresponds to the erasure of its type in the generic declaration". In the generic declaration of getThing() its type T is unbounded, which means its erasure is java.lang.Object.

Note that the spec does not say that getThing()'s type is the type constructed by erasing the raw type of which it is a member (that is, ThingProducer) -- it is actually the erasure of getThing() itself, which means that both type parameters (T and S) are erased.

[Aside: In my original answer, I quoted another sentence of the spec: "It is a compile-time error to pass type arguments to a non-static type member of a raw type that is not inherited from its superclasses or superinterfaces." My original reading of that sentence was that the compiler is required to emit a compile-time error for your syntax above, since I concluded that you were attempting to "pass type arguments to a non-static type member of a raw type". But I've changed my mind: I believe that last sentence is referring to a non-static type member (that is, a nested type), not merely a non-static generic member.]

Of course, no discussion of section 4.8 is complete without quoting this bit from the spec:

The use of raw types is allowed only as a concession to compatibility of legacy code. The use of raw types in code written after the introduction of generics into the Java programming language is strongly discouraged. It is possible that future versions of the Java programming language will disallow the use of raw types.

like image 101
Daniel Pryden Avatar answered Sep 19 '22 21:09

Daniel Pryden