Java 8中stream().map()和stream.map({})的区别

7
昨天我遇到了一些我既不太理解又找不到解释的东西:
考虑以下操作:
Stream.of(1, 2, 3).map(i -> i * 2).forEach(System.out::println);

//This one won't compile
Stream.of(1, 2, 3).map(i -> { i * 2; }).forEach(System.out::println);

似乎第二个可以扩展到
Stream.of(1, 2, 3).map(i -> { return i * 2;}).forEach(System.out::println);

它会编译通过。我提出这个问题是因为我习惯于第一种版本,但我的IDE(Netbeans)总是引用最后一个版本。

所以我的问题是:这两个实现有什么区别/优势?为什么带有{}块的需要返回值?那个值的需求在哪里(除了使代码编译)?

更新:

关于Java 8 lambda语法何时可以省略花括号,这个问题不仅仅与lambda表达式语法有关。

Stream.of(1, 2, 3).forEach(i -> { System.out.println(i); });

编译正常,因此(据我的理解)与map()的实现有关。

祝好,Ben


1
“无大括号”版本要求箭头符号右侧为有效的Java表达式(注意:是表达式,而不是语句!);这就是它能够工作的原因。但最终两者都会执行相同的操作。 - fge
1个回答

9
区别如下:
Lambda表达式长这样:
````

lambda表达式的形式如下:

````
parameters -> expression

或者

parameters -> { block }

在 lambda 中,block 可以返回一个值,或者对于类似 void 的行为不返回任何值。

换句话说,如果 expression 具有非 void 类型,则 parameters -> expression lambda 相当于 parameters -> { return expression; };如果 expression 具有 void 类型(例如 System.out.printf()),则相当于 parameters -> { expression; }

你的第一个版本实际上使用了一些额外的开销:

i -> i = i * 2 可以简化为 i -> i * 2,因为 i = 赋值是多余的,i 在之后立即消失而没有被进一步使用。

这就像

Integer function(Integer i) {
    i = i * 2;
    return i;
}

或者

Integer function(Integer i) {
    return (i = i * 2);
}

可以简化为:
Integer function(Integer i) {
    return i * 2;
}

所有这些例子都符合接口UnaryOperator<Integer>,它是Function<Integer, Integer>的特殊情况。
相比之下,你的第二个例子就像...
XY function(int i) {
    i = i * 2;
}

无效的代码:

  • 要么 XYvoid(这将产生一个不符合 .map()Consumer<Integer>
  • 要么 XY 确实是 Integer(那么缺少返回语句)。

那个值有什么必要吗(除了使代码编译)?

好吧,.forEach(System.out::println); 需要那个值...

因此,所有可以转换为 Function<T, R> 的内容都可以提供给 T 流的 .map(),从而得到一个 R 流:

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

将您提供的“整数(Integer)”转换为另一个“整数(Integer)”,并给您另一个“流(Stream)<整数(Integer)>”。您注意到它们是装箱的吗?
其他方法包括:
// turn a Stream<Integer> to an IntStream with a 
// int applyAsInt(Integer i) aka ToIntFunction<Integer>
Stream.of(1, 2, 3).mapToInt(i -> i * 2)
Stream.of(1, 2, 3).mapToInt(i -> { return i * 2; })

// turn an IntStream to a different IntStream with a 
// int applyAsInt(int i) aka IntUnaryOperator
IntStream.of(1, 2, 3).map(i -> i * 2)
IntStream.of(1, 2, 3).map(i -> { return i * 2; })

// turn an IntStream to a Stream<Integer> with a 
// Integer apply(int i) aka IntFunction<Integer>
IntStream.of(1, 2, 3).mapToObj(i -> i * 2)
IntStream.of(1, 2, 3).mapToObj(i -> { return i * 2; })

所有这些例子的共同点是它们获取一个值并生成相同或不同类型的值。(注意这些示例根据需要使用自动装箱和自动拆箱。)
另一方面,任何可以转换为Consumer<T>的内容都可以提供给T流的.map(),它可以是任何形式的lambda表达式,该表达式生成一个void表达式:
.forEach(x -> System.out.println(x))
.forEach(x -> { System.out.println(x); }) // no return as you cannot return a void expression
.forEach(System.out::println) // shorter syntax for the same thing
.forEach(x -> { }) // just swallow the value

有了这个想法,很容易看出带有 void 表达式类型的 Lambda 无法传递给 .map(),而带有非 void 类型的 Lambda 也无法传递给 forEach()


3
即使没有终端的 .forEach(System.out::println)map(…) 期望一个 Function,这就足以要求Lambda表达式返回一个值... - Holger
我编辑了我的问题,你可能也需要编辑你的答案 :) - Ben Win
@BenWin 我试图为此提供一些启示,希望我已经成功了... - glglgl
非常感谢您提供如此详尽而优质的答案! - Ben Win

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接