views:

133

answers:

3

The following code compiles perfectly with Eclipse, but fails to compile with javac:

public class HowBizarre {
      public static <P extends Number, T extends P> void doIt(P value) {
      }

      public static void main(String[] args) {
            doIt(null);
      }
}

I simplified the code, so T is not used at all now. Still, I don't see a reason for the error. For some reason javac decides that T stands for Object, and then complains that Object does not conform to the bounds of T (which is true):

HowBizarre.java:6: incompatible types; inferred type argument(s) java.lang.Number,java.lang.Object do not conform to bounds of type variable (s) P,T

found : <P,T>void

required: void

       doIt(null);
           ^

Note that if I replace the null parameter with a non-null value, it compiles fine.

Which of the compilers behaves correctly and why? Is this a bug of one of them?

+2  A: 

It's rather a bug in javac. Eclipse infers the correct type.

You can work it around by calling doIt((Number) null);

Even if you don't plan to use javac for development, fix this issue, because tools like ant or maven use it and it will cause problems in case you introduce them at some point.

Bozho
I remember having to use a cast with null in the past with Java, but I can't remember why... is there any other situation where you have to do that?
JAB
yes, there was something when using reflection.
Bozho
@JAB casting nulls is occasionally done when calling overloaded methods. For example, if you have two methods, `foo(String)` and `foo(Number)`, you can't say `foo(null)`, as it's ambiguous. Typically you'd cast the null literal to select the method you want. Alternatively, you could use a variable of the appropriate static type whose value happens to be null, but that's more verbose and probably not any clearer.
Laurence Gonsalves
@Laurence: That may be it.
JAB
@JAB: you can also cast `null` when dealing with varargs; http://stackoverflow.com/questions/2888305/difference-between-double-and-double-in-formal-parameter-type-declaration/2889377#2889377 vararg gotchas/`count((Object) null);`
polygenelubricants
no, compiler should not infer the type of `null`
irreputable
@irreputable wouldn't you agree that casting null doesn't look good ;)
Bozho
+6  A: 

The problem is due to a JLS specification that mandates that otherwise uninferrable type arguments must be inferred as Object, even if it doesn't satisfy the bounds (and would consequently trigger a compilation error).

The following is an excerpt from the "bug" report (which has been further annotated for clarity):

"Bug" ID 6299211 - method type variable: inference broken for null

This program does not compile:

public class Try {
    void m() {
        java.util.Collections.max(null);
    }
}

State: CLOSED, NOT A DEFECT.

Evaluation: THIS IS NOT A BUG. The inference algorithm cannot gather any information from the argument (null) and the method is not called in a place where there are any expectations on the returned value. In such cases the compiler must infer java.lang.Object for the type variable.


JLS 15.12.2.8 Inferring Unresolved Type Arguments

Any remaining type variables that have not yet been inferred are then inferred to have type Object


However, Object is not a subtype of Comparable<? super Object> and thus not within the bounds of the type variable in the declaration of Collections.max:

<T extendsObject & Comparable<? super T>> T max(Collection<? extends T>)


Further explorations

Using explicit type parameters "fixes" the problem:

HowBizarre.<Number,Integer>doIt(null); // compiles fine in javac

To show that this has less to do with a null argument and more to do with the absolute lack of information for type inferrence, you can try e.g. either of the following declarations:

<T,U extends Comparable<T>> void doIt()

<T extends Number,U extends T> void doIt()

In either case, an invocation doIt(); doesn't compile in javac, as it must infer U to be Object as per 15.12.2.8, even if doing so would trigger a compilation error.


Note on Eclipse

While none of the snippets above compile in some version of javac, they all do in some version of Eclipse. This would suggest a bug on Eclipse's part. It's been known that there are disagreements between the different compilers.

Related questions

polygenelubricants
I don't agree with the evaluation. `null` should not be inferred as `Object`. It should be inferred as "anything that matches".
Bozho
CW-ed. Feel free to edit, copy, whatever.
polygenelubricants
+1 for finding the relevant JLS section. To which I also don't agree, but that's unimportant :)
Bozho
what do you mean you don't agree with JLS?:) Java generics is a house of cards carefully constructed, it is never fair to look at one card and say it is placed wrongly.
irreputable
java generics is a card castle. One small blow and it breaks. But that's another topic. It is obvious that it is possible to infer the correct type (eclipse does it).
Bozho
so what do you propose to change the spec to allow it? "compiler should try as hard as it can to infer a consistent story that pleases common senses"? Sure, eclipse compiler has its own mechanical and deterministic inference algorithm, too bad it's wrong according to the spec. You cannot look at one example that pleases you and approve the whole algorithm.
irreputable
Thanks a lot for the information. Though I tend to agree with Bozho that this is a bug in javac, I have no intentions of fully understanding the comiler spec, in order to prove/dispute this. I will use a workaround this time, and maybe report a bug (in the non-simplified version of my problem there are more parameters to use for type inference and still it fails).
Eyal Schneider
+1  A: 

from polygenelubricants' research, sun's javac is apparently faithful to the spec. in the past I also used other compilers and when there's a conflict, it always turned out that sun's javac is correct. Sun has the advantage of documenting their experience from implementation into the spec, while the other guys have to read the spec from scratch - it is really hard not to fall in sleep when you read it.

irreputable