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

How does java stream.peek() affect the 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。

我的假设是,这一定意味着该操作由于 peek 调用而以某种方式分离。像

if(i%2==0)
  peek
  if(i%3==0)

这是真的吗?如果是的话会影响性能吗(我假设不会)。

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

Stream API 是一个 ordinary Java API, as you can see yourself. It’s filter method 接收任意 Predicate 实例,无论是通过 lambda 表达式还是普通 class(或 enum 来命名所有的可能性)。

如果您随后调用 filter 两次,底层实现 可以 通过调用 Predicate.and 将它们连接到一个过滤器,但无论是否这样做对于通过 lambda 表达式实现的谓词没有任何影响。

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

因此,这些可能的实现之间没有实质性差异,如果您在中间插入另一个操作,例如将 Consumer 传递给 peek,则存在 none。当然,它现在比没有这个动作做的更多,所以它 性能影响,但不涉及谓词。

但您的普遍误解似乎是您认为以下两者之间存在重大差异:

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

看看编译方法的字节码:

//  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

如您所见,打印语句的插入导致打印语句的插入,仅此而已。或者,换句话说,&& 运算符并不是一个与两个嵌套的 if 语句不同的神奇融合。两者在语义和字节码方面完全相同。

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