Reputation: 5168
I am getting Ambiguity error while trying to print result of JAVA8 collectors.
I am trying to print result of summation of IDs in Product
object, but getting the following error :
"The method println(double) is ambiguous for the type PrintStream"
Here is a small line of code where I am getting the compilation error:
Edited : Adding code snippet for more details :
package com.sample.reproduce.bugs;
public class Product {
private double id;
private String productName;
public double getId() {
return id;
}
public void setId(double id) {
this.id = id;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
}
Following is the line of code where I am getting compilation error :
System.out.println(productList.stream().collect(Collectors.summingDouble(x -> x.getId())));
Class Snapshot :
I don't get any error if I will use Collector in a separate line (out of println
method).
Why ins't the compiler able to detect the exact return type of JAVA 8 Collectors if we use it in println()
method?
Adding details of another approach with command prompt :
I tried with command prompt with same JDK version and the program was compiled and executed successfully. So the answer by Holger seems correct. This seems issue only with Eclipse compiler :
Upvotes: 4
Views: 414
Reputation: 8178
Yes, this is a compiler bug, but closer investigation suggests that this may be caused by an omission in JLS.
More specifically, the bug would go away if one sentence in JLS §18.5.2.2. were changed like this:
Old:
For a poly class instance creation expression or a poly method invocation expression , C contains all the constraint formulas that would appear in the set C generated by §18.5.2 when inferring the poly expression's invocation type.
Mentioning only "constraint forumulas" seems to be insufficient.
Proposed new:
For a poly class instance creation expression or a poly method invocation expression , C contains all the type bounds and capture bounds that would result from reducing and incorporating the set C generated by §18.5.2 when inferring the poly expression's invocation type.
PS: Javac is known for implementing more / different data flows between inner and outer inference than are captured in JLS, which is probably the reason why javac selects println(Object)
. In some regards this implementation may be closer to the intended semantics, and in the example in this question common sense agrees with javac. That's why focus should IMHO be on improving JLS (and transitively ecj).
EDIT: While the above analysis is plausable, fixes the issue, and perhaps even matches what javac is actually doing, it cannot explain, why the problem only occurs below overload resolution for println(..)
but not in an assignment to a char[]
variable.
After more research into this difference, an alternate change was crafted, that will effectively (via several indirections) force the compiler to re-compute the capture bound instead of passing it up like proposed above. This change is in line with the current JLS. The exact chain of causality of this problem is beyond the scope of this forum, but interested parties are invited to read some background in the Eclipse bug linked above.
Upvotes: 2
Reputation: 298123
This is a bug in the Eclipse compiler, and the rabbit hole goes even deeper than the compiler error. I reduced your code example to
public static void main(String[] args)
{
println(Stream.of(42).collect(Collectors.summingDouble(d -> d)));
}
public static void println(double x) {}
public static void println(char[] x) {}
public static void println(String x) {}
public static void println(Object x) {}
I only kept the the println
methods affecting the behavior of the compiler.
There are the methods println(Object x)
, which is the one which should be invoked, as it is the only one applicable without boxing operations, println(double)
, which is the one mentioned in the error message and applicable after unboxing, and the two methods println(char[] x)
and println(String x)
, which are not applicable at all.
Removing the println(double x)
method makes the error go away, which would be understandable, even if the error is not correct, but weirdly, removing the println(Object x)
method does not solve the error.
And even worse, removing either of the inapplicable methods, println(char[] x)
or println(String x)
, also removes the error, but generates code invoking the wrong, inapplicable method:
public static void main(String[] args)
{
println(Stream.of(42).collect(Collectors.summingDouble(d -> d)));
}
public static void println(double x) { System.out.println("println(double)"); }
public static void println(char[] x) { System.out.println("println(char[])"); }
//public static void println(String x) { System.out.println("println(String)"); }
public static void println(Object x) { System.out.println("println(Object)"); }
Exception in thread "main" java.lang.ClassCastException: java.lang.Double cannot be cast to [C
at Tmp2.main(Unknown Source)
public static void main(String[] args)
{
println(Stream.of(42).collect(Collectors.summingDouble(d -> d)));
}
public static void println(double x) { System.out.println("println(double)"); }
//public static void println(char[] x) { System.out.println("println(char[])"); }
public static void println(String x) { System.out.println("println(String)"); }
public static void println(Object x) { System.out.println("println(Object)"); }
Exception in thread "main" java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.String
at Tmp2.main(Unknown Source)
I think, we don’t need to dig into the depth of the formal Java Language Specification, to recognize this behavior as inappropriate.
Removing both inapplicable methods, println(char[] x)
and println(String x)
, makes the compiler choose the correct method, println(Object x)
over println(double x)
, but that’s not impressive.
For reference, I tested with version Oxygen.3a Release (4.7.3a), build 20180405-1200. There are likely other versions affected as well.
Upvotes: 9
Reputation: 109547
System.out.println(productsList.stream().mapToDouble(x -> x.id).sum());
I am not entire sure about the exact code here, but without a required type (println
has many overloaded parameters), and generic typing of a stream, ambiguity arises. Especially with a Double id
instead of a double
. Maybe someone else can do a better explanation.
Assigment to a local variable might have worked.
Better is to use the streams of the primitive type. The above uses a DoubleStream
. For an "id" I would rather have expected a LongStream.
Upvotes: 0