Reputation: 53
The following code compiles in Eclipse Version: Mars.2 Release (4.5.2) Build id: 20160218-0600 on Windows 10 Version 10.0.14393. It does not compile on a range of oracle javac compilers. There are other questions concerning pieces of code that compile with the Eclipse JDT compiler, but not javac. I could not find one similar to this example. This is a piece of toy code, and it's only purpose is to demonstrate this curiosity.
Is the eclipse compiler correct to compile this?
Note: If the eclipse compiler generated .class is de-compiled it produces source which can be compiled with javac.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class Puzzle {
@SuppressWarnings({ "rawtypes", "unchecked" })
public static void main(String[] args) {
List<List> outer = Arrays.asList(Arrays.asList(new Object(), new Object()),
Arrays.asList(),
Arrays.asList(new Object(), new Object()));
Stream<Boolean> bs1 = outer.stream().flatMap(inner -> inner.stream()).map(obj -> obj.hashCode() % 2 == 0);
boolean b0 = bs1.filter(b -> !b).findAny().isPresent();
boolean b2 = outer.stream().flatMap(inner->inner.stream())
.map(obj -> obj.hashCode() % 2 == 0)
.filter(b -> !b).findAny().isPresent();
System.out.printf("%s %s %s", outer, b0, b2);
}
}
Here is the compiler error, in several versions of the compiler:
C:\Users\tofti>C:\jdk1.8.0_121\bin\javac -version & C:\jdk1.8.0_121\bin\javac Puzzle.java
javac 1.8.0_121
Puzzle.java:23: error: bad operand type Object for unary operator '!'
.filter(b -> !b).findAny().isPresent();
^
1 error
C:\Users\tofti>C:\jdk1.8.0_112\bin\javac -version & C:\jdk1.8.0_112\bin\javac Puzzle.java
javac 1.8.0_112
Puzzle.java:23: error: bad operand type Object for unary operator '!'
.filter(b -> !b).findAny().isPresent();
^
1 error
C:\Users\tofti>C:\jdk1.8.0_141\bin\javac -version & C:\jdk1.8.0_141\bin\javac Puzzle.java
javac 1.8.0_141
Puzzle.java:23: error: bad operand type Object for unary operator '!'
.filter(b -> !b).findAny().isPresent();
^
1 error
And here is the code compiling, and executing in Eclipse: Code compiling and running in eclipse
Upvotes: 4
Views: 245
Reputation: 3453
One of the suppressed "unchecked" warnings reveals the problem, in Eclipse 4.6.3:
Type safety: The expression of type Stream needs unchecked conversion to conform to Stream<Boolean>
And in javac 1.8.0_131:
Puzzle.java:12: warning: [unchecked] unchecked conversion
Stream<Boolean> bs1 = outer.stream().flatMap(inner -> inner.stre
am()).map(obj -> obj.hashCode() % 2 == 0);
^
required: Stream<Boolean>
found: Stream
Without any casting, here are the actual return types from each method call in the chain:
Stream<List> s1 = outer.stream();
Stream s2 = s1.flatMap(inner -> inner.stream());
Stream s3 = s2.map(obj -> obj.hashCode() % 2 == 0);
Stream s4 = s3.filter(b -> !b); // compiler error
Optional opt = s4.findAny();
boolean b = opt.isPresent();
If you add a wildcard to make it List<List<?>> outer
, this is what you get instead:
Stream<List<?>> s1 = outer.stream();
Stream<?> s2 = s1.flatMap(inner -> inner.stream());
Stream<Boolean> s3 = s2.map(obj -> obj.hashCode() % 2 == 0);
Stream<Boolean> s4 = s3.filter(b -> !b); // compiles OK
Optional<Boolean> opt = s4.findAny();
boolean b = opt.isPresent();
So the issue is that the .map
call on the raw Stream
doesn't actually return Stream<Boolean>
, you're just implicitly casting it when you assign bs1
, and that fixes the raw types in the rest of the chain. In fact, you can add this cast to the one-line version and it compiles:
boolean b2 = ((Stream<Boolean>) outer.stream().flatMap(inner -> inner.stream())
.map(obj -> obj.hashCode() % 2 == 0))
.filter(b -> !b).findAny().isPresent();
Now, the raw .map
call is missing the class type parameter <T>
, but <R>
is a method type parameter, so why doesn't it return Stream<R>
? The raw types section in the Java language specification states that "a non-static type member of a raw type is considered raw". The example given is a generic inner class, but assuming it applies to generic methods as well, this should be the reason for the <R>
parameter of .map
getting dropped when it's called on a raw Stream
, causing it to return another raw Stream
.
EDIT: Found this in an Eclipse bug report:
It looks like javac infers the return type of the method invocation to be the raw type I when passed in arguments are raw types. This may be motivated by the almost last bit of §18.5.2 after bounds set 4 has been generated
[...]
If unchecked conversion was necessary for the method to be applicable during constraint set reduction in §18.5.1, then the parameter types of the invocation type of m are obtained by applying θ' to the parameter types of m's type, and the return type and thrown types of the invocation type of m are given by the erasure of the return type and thrown types of m's type.
Upvotes: 4