尝试打印 JAVA8 收集器的结果时出现歧义错误

Ambiguity error while trying to print result of JAVA8 Collector

我在尝试打印 JAVA8 个收集器的结果时出现歧义错误。

我正在尝试打印 Product 对象中 ID 的总和结果,但出现以下错误:

"The method println(double) is ambiguous for the type PrintStream"

这是我遇到编译错误的一小行代码:

已编辑:添加代码片段以获取更多详细信息:

  1. Product.java 域 class.

包 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;
    }

}
  1. Main.java class 我遇到编译错误的地方:

以下是我遇到编译错误的代码行:

System.out.println(productList.stream().collect(Collectors.summingDouble(x -> x.getId())));

Class 快照:

如果我将在单独的行中使用 Collector(在 println 方法之外),我不会收到任何错误。

如果我们在 println() 方法中使用它,为什么编译器无法检测到 JAVA 8 个收集器的确切 return 类型?

使用命令提示符添加另一种方法的详细信息:

我尝试使用相同 JDK 版本的命令提示符,程序编译并成功执行。所以霍尔格的回答似乎是正确的。这似乎只与 Eclipse 编译器有关:

System.out.println(productsList.stream().mapToDouble(x -> x.id).sum());

我不完全确定这里的确切代码,但是没有 必需的 类型(println 有很多重载参数)和流的通用类型,出现歧义。特别是 Double id 而不是 double也许其他人可以做更好的解释。

对局部变量的赋值可能有效。

更好的是使用原始类型的流。上面使用了一个DoubleStream。对于 "id",我宁愿期待 LongStream。

这是 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) 方法不会 解决错误。

更糟,删除或者不适用的方法,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),build 20180405-1200。可能还有其他版本受到影响。

是的,这是一个compiler bug,但仔细调查表明这可能是由于 JLS 中的一个遗漏造成的。

更具体地说,如果 JLS §18.5.2.2. 中的一个句子像这样更改,错误就会消失:

旧:

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.

仅提及"constraint forumulas"似乎还不够。

提议的新:

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 以在内部和外部推理之间实现比在 JLS 中捕获的更多/不同的数据流而闻名,这可能是 javac 选择 println(Object) 的原因.在某些方面,此实现可能更接近预期的语义,并且在这个问题的示例中,常识与 javac 一致。这就是为什么恕我直言,重点应该放在改进 JLS(以及传递性 ecj)上。

编辑:虽然上面的分析是合理的,解决了问题,甚至可能与 javac 实际做的相匹配,但它无法解释,为什么问题只发生在重载解决方案之下对于 println(..) 但不在对 char[] 变量的赋值中。

在对这种差异进行更多研究之后,设计了一个替代更改,这将有效地(通过几个间接)强制编译器重新计算捕获边界,而不是像上面提议的那样传递它。此更改符合当前的 JLS。此问题的确切因果关系超出了本论坛的范围,但邀请感兴趣的各方阅读上面链接的 Eclipse 错误中的一些背景知识。