答案内容如下:
随着时间的推移,答案已经增长了不少,因此我将从摘要开始:
观察
- 流API实际执行的跟踪起始看起来很可怕。许多调用和对象创建。请注意,但是集合中所有元素的唯一重复部分是do-while循环的主体。因此,除了一些常量开销外,每个元素的开销约为6个虚拟方法调用(
invokeinterface
指令-我们的两个lambda和4个sinks上的accept()
调用)。
- 传递给流API调用的lambda被翻译为包含实现和一个
invokedynamic
指令的静态方法。它不是创建新对象,而是提供了运行时创建lambda的说明。在之后调用创建的lambda对象上的lambda方法没有什么特别的(invokeinterface
指令)。
- 您可以观察到流是如何懒惰地评估的。
filter()
和map()
将其操作包装在继承自ReferencePipeline
,AbstractPipeline
和最终BaseStream
的匿名子类中。实际评估是在执行collect()
时完成的。
- 您可以看到流真正使用的是
Spliterator
而不是Iterator
。请注意检查isParallel()
的许多分支-并行分支将利用Spliterator
的方法。
- 有相当多的新对象被创建,至少13个。如果您在循环中调用此类代码,则可能会遇到垃圾收集问题。对于单次执行应该没问题。
- 我想看到两个版本的基准比较。流版本可能会慢一些,差异从“Java 7版本”增加到增加的鱼的数量。还请参见相关的SO问题。
跟踪流示例中流使用的执行过程
下面的伪代码记录了使用流的版本的执行跟踪。请参见本文底部的说明以了解如何阅读跟踪。
Stream stream1 = fishList.stream();
Spliterator spliterator = fishList.spliterator();
return Spliterators.spliterator(fishList.a, 0);
return new ArraySpliterator(fishList, 0);
return StreamSupport.stream(spliterator, false)
return new ReferencePipeline.Head(spliterator, StreamOpFlag.fromCharacteristics(spliterator), false)
Predicate fishPredicate =
Stream stream2 = stream1.filter(fishPredicate);
return new StatelessOp(this, StreamShape.REFERENCE, StreamOpFlag.NOT_SIZED) { }
Function fishFunction =
Stream stream3 = stream2.map(fishFunction);
return new StatelessOp(this, StreamShape.REFERENCE, StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) { }
Collector collector = Collectors.toList();
Supplier supplier =
BiConsumer accumulator =
BinaryOperator combiner =
return new CollectorImpl<>(supplier, accumulator, combiner, CH_ID);
List hasFishList = stream3.collect(collector)
List container;
if (stream3.isParallel() && ) { }
else {
TerminalOp terminalOp = ReduceOps.makeRef(collector)
Supplier supplier = Objects.requireNonNull(collector).supplier();
BiConsumer accumulator = collector.accumulator();
BinaryOperator combiner = collector.combiner();
return new ReduceOp(StreamShape.REFERENCE) { }
container = stream3.evaluate(terminalOp);
if (linkedOrConsumed) { }
linkedOrConsumed = true;
if (isParallel()) { }
else {
Spliterator spliterator2 = sourceSpliterator(terminalOp.getOpFlags())
if (sourceStage.sourceSpliterator != null) { }
if (isParallel()) { }
return spliterator;
terminalOp.evaluateSequential(stream3, spliterator2);
ReducingSink sink = terminalOp.makeSink()
return new ReducingSink()
Sink sink = terminalOp.wrapAndCopyInto(sink, spliterator)
Sink wrappedSink = wrapSink(sink)
for () { p.opWrapSink(p.previousStage.combinedFlags, sink) }
return new Sink.ChainedReference(sink)
terminalOp.copyInto(wrappedSink, spliterator);
if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {
wrappedSink.begin(spliterator.getExactSizeIfKnown());
supplier.get()
spliterator.forEachRemaining(wrappedSink)
!! do {
action.accept((String)a[i])
} while (++i < hi)
wrappedSink.end()
} else { }
return sink;
return sink.get()
}
if (collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return container; }
else { }
感叹号指向真正的工作马: do-while循环在fishList
的Spliterator
中。以下是更详细的do-while循环追踪:
do {
action.accept((String)a[i])
if (predicate.test(u)) { downstream.accept(u); }
downstream.accept(mapper.apply(u));
accumulator.accept(u)
} while (++i < hi)
使用Lambda的流API字节码级别解析
让我们看一下执行代码的相关部分在字节码中的样子。有趣的部分是如何运用Lambda表达式。
fishList.stream().filter(f -> f.contains("fish")).map(f -> f.substring(0, 1).toUpperCase() + f.ubstring(1)).collect(Collectors.toList());
被翻译了。您可以在pastebin上找到完整版本。我将只专注于这里的filter(f -> f.contains("fish"))
:
invokedynamic #26, 0 // InvokeDynamic #0:test:()Ljava/util/function/Predicate; [
java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
(Ljava/lang/Object;)Z,
FishTest.lambda$fish8$0(Ljava/lang/String;)Z,
(Ljava/lang/String;)Z
]
invokeinterface #27, 2 // InterfaceMethod java/util/stream/Stream.filter:(Ljava/util/function/Predicate;)Ljava/util/stream/Stream;
这里没有 与流 API 有关的具体内容,但使用了 新的 invokedynamic
指令 来创建 Lambda 表达式。Java 7 中 Lambda 的等价物是创建实现 Predicate
的匿名内部类。这将被转换为字节码:
new FishTest$1 // create new instance of Predicate
dup
invokespecial FishTest$1.<init>()V // call constructor
在Java 8中创建lambda被翻译为单个
invokedynamic
指令,而不需要创建新对象。
invokedynamic
指令的目的是将lambda的创建推迟到运行时(而不是编译时)。这使得像
缓存lambda实例之类的功能成为可能。
invokedynamic
的参数给出了构造相应函数接口实例的“配方”。它们代表运行时实例创建的元工厂,实现其方法的引用(即
Predicate.test()
)和方法的实现。在我们的情况下,实现是调用静态方法
boolean lambda$fish8$0(String)
,编译器将其添加到我们的类中。它包含
f.contains("fish")
的实际字节码。如果您使用了捕获外部作用域的lambda引用(例如
list::add
)、捕获变量等,则事情会变得更加复杂-请查看
此文档中的“indy”出现次数以获取更多信息。
字节码的其他部分则不太有趣。do-while循环除了明显的循环外,还包含一个调用Consumer
上的accept()
的invokeinterface
指令。 accept()
调用沿着sinks传播,依次调用我们的lambda。没有什么特别的,lambda调用和通过sinks的传播都是简单的invokeinterface
指令。
如何阅读伪代码
缩进用于显示缩进代码上方展开的身体。以/*>*/
开头的代码表示当前调用的延续(如果需要为了更好的可读性)。因此,调用
Objects.requireNonNull(new Object());
在跟踪伪代码中将被编写为:
Object o = new Object();
Objects.requireNonNull(o);
if (o == null) {
throw new NullPointerException();
}
return o;
我还跳过了一些不重要的调用,如空值检查、省略泛型参数,在适当的地方提取内联表达式到变量中等,以提高可读性。
new FunctionalInterface() { public X method(...) {...your lambda code...}}
。实际情况更加复杂,但本质上并非如此--它只是为实际实现提供更多的灵活性。Tagir告诉你的与这个细节有关,与Streams API无关。 - Marko Topolnik