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>
voidrequired: 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?
A Java compiler applies strong type checking to generic code and issues errors if the code violates type safety. Fixing compile-time errors is easier than fixing runtime errors, which can be difficult to find. Elimination of casts. Enabling programmers to implement generic algorithms.
Generic methods are methods that introduce their own type parameters. This is similar to declaring a generic type, but the type parameter's scope is limited to the method where it is declared. Static and non-static generic methods are allowed, as well as generic class constructors.
Generic Types Differ Based on Their Type Arguments: Consider the following Java code.
As with generic methods, the type parameter section of a generic class can have one or more type parameters separated by commas.
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 inferjava.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 ofComparable<? super Object>
and thus not within the bounds of the type variable in the declaration ofCollections.max
:
<T extends
Object & Comparable<? super T>
> T max(Collection<? extends T>)
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With