【问题标题】:Why does .forEach(val -> list.add()) compile whereas .forEach(val -> true) doesn't? [duplicate]为什么 .forEach(val -> list.add()) 编译而 .forEach(val -> true) 不编译? [复制]
【发布时间】:2018-12-15 15:58:59
【问题描述】:

最好在代码中表达这种行为:

List<Integer> list= new ArrayList<>();
Stream.of(1,2,3).forEach(i -> list.add(1));  // COMPILES

Stream.of(1,2,3).forEach(i -> true);  // DOES NOT COMPILE!

forEach(...)接受Consumer,但是如果List接口有以下签名boolean add(E e),为什么第一个例子编译?而第二个产生:

lambda 表达式中的返回类型错误:布尔值无法转换为 无效

【问题讨论】:

  • 你想用第二个语句达到什么目的?
  • 提示:Stream.of(1,2,3).forEach(i -&gt; {return list.add(1);}) 也无法编译。提示2:什么方法(X -&gt; Y映射)Consumer&lt;T&gt;代表(看看它包含的抽象方法,它接受什么类型的参数以及它的返回类型)?
  • 与其简单地声明“不编译”,不如显示您收到的精确错误消息会好得多。特别是,至少有一个答案是在假设您遇到语法错误的情况下运行的(尽管在这种情况下,“不解析”会比“不编译”更正确),但我不认为语法错误就是问题所在。
  • @JörgWMittag 编译错误打印incompatible types: bad return type in lambda expression missing return value。 @Pshemo 在我开始这个线程之前我检查了签名是什么。 Consumer.accept(T t) 应该返回 void (docs.oracle.com/javase/8/docs/api/java/util/function/…)
  • @LeonidDashko 当我在 IntelliJ IDEA 上运行它时,我收到了我在帖子中包含的错误。但是,由于它与您刚刚提到的内容不同,请考虑使用确切信息更新帖子。

标签: java java-8 compiler-errors java-stream


【解决方案1】:

虽然你可能只是在寻找

Stream.of(1,2,3).forEach(list::add); // adding all to `list`

如果 List 接口有以下内容,为什么第一个示例编译 签名布尔加(E e)

主要是因为方法的返回类型在第一次调用中被忽略了。这就是它的扩展:

Stream.of(1,2,3).forEach(new Consumer<Integer>() {
    @Override
    public void accept(Integer i) {
        list.add(1); // ignored return type
    }
});  // COMPILES

另一方面,另一种 lambda 表示更像是 Predicate(也是 FunctionalInterface),表示始终从其 test 方法返回 true。如果您甚至尝试将其表示为 Consumer,它可能看起来就像

Stream.of(1,2,3).forEach(new Consumer<Integer>() {
    @Override
    public void accept(Integer i) {
        return true; // you can correlate easily now why this wouldn't compile 
    }
});  // DOES NOT COMPILE!

添加到design basis via a comment from Brian

Java 允许您调用方法并忽略返回值(将方法调用表达式作为语句)。因为我们允许在 调用时,我们在将方法调整为 其参数兼容但功能性的功能接口 接口返回无效。

编辑:把它放在his own words 中,尽可能接近language spec

更准确地说,list.add(x) 是一个语句表达式,并且 因此是空兼容的。 true 不是语句表达式, 因此不兼容 void。 forEach(Consumer) 需要一个 void 兼容的 lambda。

【讨论】:

  • 更准确地说,list.add(x) 是一个语句表达式,因此是 void 兼容的。 true 不是语句表达式,因此不兼容 void。 forEach(Consumer) 需要与 void 兼容的 lambda。
【解决方案2】:

forEach() 作为Consumer 作为唯一的参数,因此您需要实现方法accept(),它返回void,所以在第二个示例中,您正在使用给定的 assinature 实现方法static boolean method(int).

【讨论】:

  • 您能解释一下,为什么list.add(1)an expression that evaluates to trueConsumer 的正确实现,而true,一个计算结果为true 的表达式是不正确的实现?
  • 就像其他人之前所说的,如果你用 { } 扩展 lambda,你会在第一种情况下得到一个表达式,在第二种情况下得到一个无效的语句。
  • 正如另一个答案中指出的那样,原因是void-compatibility,而不是语法。
  • 如果你不使用方括号,它要么是一个转换为return的值,要么是一个方法调用。
  • @JörgWMittag:这是正确的问题。答案是,只有当“lambda 的主体是语句表达式(即,允许作为语句独立的表达式)”(第 15.27.3 节)时,lambda 主体才与void 兼容,而在 Java 中,true; 不是有效的语句(对比其他语言)。
【解决方案3】:

Special Void-Compatibility Rule (JLS) 表示返回 void 的 lambda 可以接受返回非 void 的语句。在这种情况下,返回被丢弃。因此编译。

Stream.of(1,2,3).forEach(i -> list.add(1)); // return value of add is ignored

通过在 Action book 中引用 Java-8 来换一种说法:

如果 lambda 有一个语句表达式作为它的主体,它是兼容的 带有返回 void 的函数描述符(提供参数 列表也兼容)。例如,以下两行都是 合法,即使 List 的方法 add 返回一个布尔值而不是 在消费者上下文中如预期的那样无效(T -> void):

// Predicate has a boolean return
 Predicate<String> p = s -> list.add(s); 
// Consumer has a void return 
Consumer<String> b = s -> list.add(s);

至于第二个例子,这是明确地尝试返回一个布尔值,它不是预期的,因此不可能。

Stream.of(1,2,3).forEach(i -> true);  

换句话说:

Stream.of(1,2,3).forEach(i -> {return true;});

如您所知,forEach 接受消费者,因此尝试“显式”返回 boolean 应该会产生编译错误。

【讨论】:

  • 我相信这就是答案,但我仍然无法看清它。 lambda 规范明确区分了带有表达式体的 lambdas 和带有块体的 lambdas,而 void-compatibility 规则专门讨论了带有块体的 lambdas 而没有提到表达式体的 lambdas。但是,问题中的两个示例都是表达式主体 lambda。我无法准确指出规范中说明这一点的那部分。您对 Java in Action 的引用似乎与您对规范的解释方式相同,我相信这种解释是正确的,我……
  • …只是找不到 哪里 它实际上是受规范支持的。
猜你喜欢
  • 2017-10-19
  • 1970-01-01
  • 1970-01-01
  • 2013-05-02
  • 2019-09-29
  • 1970-01-01
  • 2019-12-12
  • 2016-01-24
  • 2017-01-11
相关资源
最近更新 更多