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 变体生成与循环变体完全相同的优化本机代码。
据我所知,如果我有一个带有两个过滤器的流,它们将在字节码中与 && 组合。
例如
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 变体生成与循环变体完全相同的优化本机代码。