4

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

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

“方法 println(double) 对于 PrintStream 类型不明确”

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

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

  1. Product.java 域类。

包 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 类:

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

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

类快照:

在此处输入图像描述

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

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

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

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

在此处输入图像描述

4

3 回答 3

9

这是 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)over println(double x),但这并不令人印象深刻。

作为参考,我使用版本 Oxygen.3a Release (4.7.3a) 进行了测试,构建 20180405-1200。可能还有其他版本受到影响。

于 2019-02-21T10:06:05.260 回答
2

是的,这是一个编译器错误,但仔细调查表明这可能是由 JLS 中的一个遗漏引起的。

更具体地说,如果JLS §18.5.2.2中的一句话,该错误就会消失。被改成这样:

老的:

对于 poly 类实例创建表达式或 poly 方法调用表达式,C 包含在推断 poly 表达式的调用类型时将出现在第 18.5.2 节生成的集合 C 中的所有约束公式。

仅提及“约束论坛”似乎是不够的。

新提议:

对于 poly 类实例创建表达式或 poly 方法调用表达式,C 包含在推断 poly 表达式的调用类型时减少和合并由§18.5.2 生成的集合 C 导致的所有类型边界和捕获边界。

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

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

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

于 2019-03-05T21:11:48.690 回答
0
System.out.println(productsList.stream().mapToDouble(x -> x.id).sum());

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

分配给局部变量可能有效。

更好的是使用原始类型的流。以上使用DoubleStream. 对于“id”,我宁愿期待一个 LongStream。

于 2019-02-21T08:44:05.540 回答