java stream.peek()如何影响字节码?

时间:2016-04-13 13:58:34

标签: java performance java-8 java-stream bytecode

据我所知,如果我有一个带有两个过滤器的流,它们将与&&和在字节码中。

例如

IntStream.range(1,10)
  .filter(i -> i % 2 == 0)
  .filter(i -> i % 3 == 0)
  .sum();

会像i%2 == 0&& i%3 == 0。

peek会影响这个吗?

如果您要查看第一个文件管理器,则会得到2468,如果您在第二个文件夹之后查看,则只获得6(当然)。

但如果你偷看两个地方

IntStream.range(1,10)
            .filter(integer -> integer % 2 == 0)
            .peek(i-> System.out.print(i))
            .filter(integer -> integer % 3 == 0)
            .peek(i-> System.out.print(i))
            .sum();
你得到24668.

我的假设是,这必然意味着操作在某种程度上是由于窥视调用而分开的。像

这样的东西
if(i%2==0)
  peek
  if(i%3==0)

这是真的,如果是这样会影响性能(我认为不会)。

2 个答案:

答案 0 :(得分:6)

Stream API是ordinary Java API,您可以看到自己。 It’s filter method接收任意Predicate个实例,可以通过lambda表达式实现,也可以通过普通class(或enum来命名所有可能性)。

如果您随后调用filter两次,则底层实现可以通过调用Predicate.and 将它们连接到单个过滤器,但无论它是否存在都没有后果通过lambda表达式实现谓词的情况。

与自定义Predicate实现不同,后者可以覆盖and方法,并在它们识别第二个Predicate实现的情况下提供优化的内容,为lambda表达式生成的类不会覆盖任何default方法,但只有一个abstract函数方法,Predicate.test,所以在这种情况下,调用and将获得default方法返回的内容,新的Predicate包含对两个源谓词的引用并将它们组合在一起,就像不使用Predicate.and的Stream实现一样。

因此,这些可能的实现之间没有实质性的差异,如果您在中间插入另一个传递给Consumer的{​​{1}}之类的操作则没有。当然,它现在做的不仅仅是没有这个动作,所以 会影响性能,但不会影响谓词。

但是你的一般误解似乎是你认为:

之间存在重大差异
peek

for(int i=1; i<10; i++) {
    if(i%2==0 && i%3==0)
        System.out.print(i);
}

查看已编译方法的字节代码:

for(int i=1; i<10; i++) {
    if(i%2==0) {
        System.out.print(i);
        if(i%3==0)
            System.out.print(i);
    }
}

正如您所看到的,插入print语句会导致完全插入print语句,仅此而已。或者,换句话说,// first variant second variant 0: iconst_1 0: iconst_1 1: istore_1 1: istore_1 2: iload_1 2: iload_1 3: bipush 10 3: bipush 10 5: if_icmpge 33 5: if_icmpge 40 8: iload_1 8: iload_1 9: iconst_2 9: iconst_2 10: irem 10: irem 11: ifne 27 11: ifne 34 14: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 17: iload_1 18: invokevirtual #3 // Method java/io/PrintStream.print:(I)V 14: iload_1 21: iload_1 15: iconst_3 22: iconst_3 16: irem 23: irem 17: ifne 27 24: ifne 34 20: getstatic #2 27: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 23: iload_1 30: iload_1 24: invokevirtual #3 31: invokevirtual #3 // Method java/io/PrintStream.print:(I)V 27: iinc 1, 1 34: iinc 1, 1 30: goto 2 37: goto 2 33: return 40: return 运算符不是与两个嵌套&&语句不同的神奇融合事物。两者在语义和字节代码中完全相同。

这同样适用于Stream API用法,尽管如此,代码会更复杂,因为条件表达式表示为if个实例,而插入的语句是Predicate s。但在最好的情况下,HotSpot优化器将为Stream变体生成与循环变体完全相同的优化本机代码。

答案 1 :(得分:0)

在字节码级别根本没有优化。每个lambda是一个单独的方法。 Java依靠JVM在运行时透明地优化所有内容。