尝试打印JAVA8收集器的结果时出现歧义错误。
我正在尝试打印Product
对象中ID的求和结果,但是出现以下错误:
“对于PrintStream类型,方法println(double)是不明确的”
这是一小段代码,其中出现编译错误:
已编辑:添加代码段以获取更多详细信息:
包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;
}
}
以下是我遇到编译错误的代码行:
System.out.println(productList.stream().collect(Collectors.summingDouble(x -> x.getId())));
班级快照:
如果我将在单独的行(在println
方法之外)中使用Collector,则不会出现任何错误。
如果我们在println()
方法中使用它,为什么编译器无法检测到JAVA 8收集器的确切返回类型?
通过命令提示符添加另一种方法的详细信息:
我尝试使用具有相同JDK版本的命令提示符,并且该程序已成功编译并执行。因此,霍尔格的答案似乎是正确的。似乎只有Eclipse编译器才有问题:
答案 0 :(得分:6)
这是Eclipse编译器中的一个错误,其漏洞比编译器错误还要深。我将您的代码示例简化为
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) {}
我只保留了println
方法来影响编译器的行为。
有些方法println(Object x)
是应该调用的方法,因为它是唯一一种没有装箱操作的方法,println(double)
是错误消息中提到的方法,并且适用取消装箱后,两种方法println(char[] x)
和println(String x)
完全不适用。
删除println(double x)
方法可以使错误消失,即使该错误不正确,也可以理解,但是奇怪的是,删除println(Object x)
方法确实 not 解决错误。
甚至更糟,删除了不适用的方法println(char[] x)
或println(String x)
的 ,也删除了错误,但生成了调用错误,不适用的方法:
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)
我认为,我们不需要深入研究正式的Java语言规范,就可以认为这种行为是不适当的。
删除println(char[] x)
和println(String x)
这两个不适用的方法会使编译器选择正确的方法println(Object x)
而非println(double x)
,但这并不令人印象深刻。
作为参考,我测试了Oxygen.3a Release(4.7.3a)版本,内部版本20180405-1200。可能还会影响其他版本。
答案 1 :(得分:2)
是的,这是compiler bug,但是更深入的调查表明,这可能是由于JLS的遗漏所致。
更具体地说,如果将JLS §18.5.2.2.中的一句话更改如下,该错误将消失:
旧:
对于多边形类实例创建表达式或多边形方法调用表达式,C包含在推断多边形表达式的调用类型时将出现在§18.5.2生成的集合C中的所有约束公式。
仅提及“约束论坛”似乎是不够的。
建议的新内容:
对于poly类实例创建表达式或poly方法调用表达式,C包含所有类型界限和捕获界限,这些界限和捕获界限是由于减少并合并第§18.5.2条生成的集合C而导致的推断多边形表达式的调用类型。
PS: Javac以在JLS中捕获的内部和外部推理之间实现更多/不同的数据流而闻名,这很可能是javac选择println(Object)
的原因。在某些方面,此实现可能更接近预期的语义,并且在此问题的示例中,常识与javac一致。因此,恕我直言,重点应该放在改善JLS(以及可传递的ecj)上。
编辑:尽管上述分析是合理的,但可以解决该问题,甚至可能与javac的实际行为相匹配,但无法解释为什么问题仅在{{1} },但未分配给println(..)
变量。
在对这种差异进行了更多研究之后,提出了一个替代更改,它将有效地(通过多个间接方式)迫使编译器重新计算捕获范围,而不是像上面提出的那样向上传递捕获范围。此更改与当前的JLS一致。此问题的确切因果关系超出了本论坛的范围,但是邀请有兴趣的各方阅读上面链接的Eclipse错误的一些背景知识。
答案 2 :(得分:0)
System.out.println(productsList.stream().mapToDouble(x -> x.id).sum());
我不太确定这里的确切代码,但是没有 required 类型(println
有很多重载参数),并且流的通用类型会引起歧义。特别是使用Double id
而不是double
。 也许其他人可以做一个更好的解释。
分配给局部变量可能有用。
更好的是使用原始类型的流。上面使用了DoubleStream
。对于“ id”,我宁愿期望使用LongStream。