为了使之具体化,请考虑下面的示例,在这里我需要查找所有名称中包含“fish”一词的鱼,然后将每个匹配的鱼的第一个字母大写。(是的,我知道哈格鱼不是真的鱼,但我没有匹配的鱼名了。)
List<String> fishList = Arrays.asList("catfish", "hagfish", "salmon", "tuna", "blowfish");
// Pre Java-8 solution
List<String> hasFishList = new ArrayList<String>();
for (String fish : fishList) {
if (fish.contains("fish")) {
String fishCap = fish.substring(0, 1).toUpperCase() + fish.substring(1);
hasFishList.add(fishCap);
}
}
// Java-8 solution using streams
List<String> hasFishList = fishList.stream()
.filter(f -> f.contains("fish"))
.map(f -> f.substring(0, 1).toUpperCase() + f.substring(1))
.collect(Collectors.toList());
对于这两种方法在字节码级别的本质上可能存在的差异,您可能有的任何洞察力都是很好的。一些实际的字节代码就更好了。
随着时间的推移,答案已经变得相当多了,所以我将以一个总结开始:
invokeinterface
指令-我们的2个lambdas和4个accept()
对接收器的调用)。invokedynamic
指令的静态方法。它不是创建一个新对象,而是给出了如何在运行时创建lambda的处方。之后对创建的lambda对象调用lambda方法没有什么特别之处(invokeinterface
指令)。filter()
和map()
将它们的操作包装在statelessop
的匿名子类中,这些子类又扩展了referencePipeline
、abstractpipeline
和basestream
。实际计算是在执行collection()
时完成的。spliterator
而不是iterator
。注意检查isParallel()
的许多分叉-并行分支将利用spliterator
的方法。下面的伪代码通过使用流执行版本捕获跟踪。有关如何读取跟踪的说明,请参阅本文底部。
Stream stream1 = fishList.stream();
// Collection#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 = /* new lambda f -> f.contains("fish") */
Stream stream2 = stream1.filter(fishPredicate);
return new StatelessOp(this, StreamShape.REFERENCE, StreamOpFlag.NOT_SIZED) { /* ... */ }
Function fishFunction = /* new lambda f.substring(0, 1).toUpperCase() + f.substring(1) */
Stream stream3 = stream2.map(fishFunction);
return new StatelessOp(this, StreamShape.REFERENCE, StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) { /* ... */ }
Collector collector = Collectors.toList();
Supplier supplier = /* new lambda */
BiConsumer accumulator = /* new lambda */
BinaryOperator combiner = /* new lambda */
return new CollectorImpl<>(supplier, accumulator, combiner, CH_ID);
List hasFishList = stream3.collect(collector)
// ReferencePipeline#StatelessOp#collect(Collector):
List container;
if (stream3.isParallel() && /* not executed */) { /* not executed */ }
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);
// AbstractPipeline#evaluate(TerminalOp):
if (linkedOrConsumed) { /* not executed */ }
linkedOrConsumed = true;
if (isParallel()) { /* not executed */ }
else {
/*>*/Spliterator spliterator2 = sourceSpliterator(terminalOp.getOpFlags())
// AbstractPipeline#sourceSpliterator(int):
if (sourceStage.sourceSpliterator != null) { /* not executed */ }
/* ... */
if (isParallel()) { /* not executed */ }
return spliterator;
/*>*/terminalOp.evaluateSequential(stream3, spliterator2);
// ReduceOps#ReduceOp#evaluateSequential(PipelineHelper, Spliterator):
ReducingSink sink = terminalOp.makeSink()
return new ReducingSink()
Sink sink = terminalOp.wrapAndCopyInto(sink, spliterator)
Sink wrappedSink = wrapSink(sink)
// AbstractPipeline#wrapSink(Sink)
for (/* executed twice */) { p.opWrapSink(p.previousStage.combinedFlags, sink) }
return new Sink.ChainedReference(sink)
terminalOp.copyInto(wrappedSink, spliterator);
// AbstractPipeline#copyInto()
if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {
/*>*/wrappedSink.begin(spliterator.getExactSizeIfKnown());
/*>*/ /* not important */
/*>*/supplier.get() // initializes ArrayList
/*>*/spliterator.forEachRemaining(wrappedSink)
// Spliterators#ArraySpliterator#foreachRemaining(Consumer):
// ... unimportant code
!! do {
/*>*/action.accept((String)a[i])
} while (++i < hi) // for each fish :)
/*>*/wrappedSink.end() // no-op
} else { /* not executed */}
return sink;
return sink.get()
}
/*>*/if (collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return container; }
/*>*/else { /* not executed */ }
感叹号指向实际的工作:fishlist
的spliterator
中的do-while循环。下面是do-while循环的更详细跟踪:
do {
/*>*/action.accept((String)a[i])
if (predicate.test(u)) { downstream.accept(u); } // predicate is our fishPredicate
downstream.accept(mapper.apply(u)); // mapper is our fishFunction
accumulator.accept(u)
// calls add(u) on resulting ArrayList
} while (++i < hi) // for each fish :)
让我们看看执行的代码的相关部分在字节码中是什么样子的。有趣的是
fishList.stream().filter(f -> f.contains("fish")).map(f -> f.substring(0, 1).toUpperCase() + f.ubstring(1)).collect(Collectors.toList());
是翻译过来的。你可以在Pastebin上找到完整的版本。在这里,我将只关注筛选器(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;
new FishTest$1 // create new instance of Predicate
dup
invokespecial FishTest$1.<init>()V // call constructor
字节码的其他部分就不那么有趣了。do-while循环除了明显的循环之外,还包含一个invokeinterface
指令,该指令在相应的使用者
上调用accept()
。accept()
调用沿着接收器传播,沿途调用lambda。这里没有什么特别之处,lambda调用和通过接收器的传播都是简单的invokeinterface
指令。
缩进用于在缩进代码上方显示调用的展开体。使用/*>*/
进行代码乞讨表示当前调用的继续(当需要更好的可读性时)。因此调用
Objects.requireNonNull(new Object());
将在跟踪伪代码中写入为:
Object o = new Object(); // extracted variable to improve visibility of new instance creation
Objects.requireNonNull(o);
// this is the body of Objects.requireNonNull():
if (o == null) {
/*>*/throw new NullPointerException(); // this line is still part of requireNonNull() body
}
return o;
除了基本的读写操作, ByteBuf 还提供了它所包含的数据的修改方法。 随机访问索引 ByteBuf 使用zero-based 的 indexing(从0开始的索引),第一个字节的索引是 0,最后一个字节的索引是 ByteBuf 的 capacity - 1,下面代码是遍历 ByteBuf 的所有字节: Listing 5.6 Access data ByteBuf buffer = ...;
问题内容: 我遇到了一些有关JVM / JIT活动的参考,其中似乎在编译字节码和解释字节码之间有区别。该特定注释声明的字节码在前10000次运行时进行解释,然后进行编译。 “编译”和“解释”字节码之间有什么区别? 问题答案: 解释字节码基本上是逐行读取字节码,不进行任何优化或任何操作,然后对其进行解析并实时执行。由于许多原因,这种方法效率低下,其中包括Java字节码设计得不能快速解释的问题。 编译
Java反射API包含一个方法
我正在使用java 11处理一些依赖项并编译到旧版本。我将一个依赖项迁移到Java11并正常工作,但我们仍然必须在Java8上运行Tomcat 7或8。是否可以使用标志来编译使用,或并在8上运行的代码? 发布标志表明应该可以: --发布版本 针对特定 VM 版本的公共、受支持和记录的 API 进行编译。支持的版本目标为 6、7、8 和 9。 这个项目是一个依赖项,独立运行在SprinBoot2.1
主要内容:1 Java8 Base64编码解码的介绍,2 基本编码和解码,3 URL和文件名的编码解码,4 MIME,5 Base64的内部类,6 Base64的方法,7 Base64.Decoder的方法,8 Base64.Encoder的方法,9 Java Base64案例:基本编码和解码,10 Java Base64案例:URL编码和解码,11 Java Base64案例:MIME编码和解码1 Java8 Base64编码解码的介绍 Java提供了一个Base64类来处理加密。您可以使用提
问题内容: 我经常卡在没有源的Java类文件中,并且试图理解我手头的问题。 请注意,反编译器是有用的,但在所有情况下都不足够… 我有两个问题 有哪些工具可用来查看Java字节码(最好从linux命令行中获得) 什么是熟悉Java字节码语法的良好参考 问题答案: 与其直接查看Java字节码(需要熟悉Java虚拟机及其操作),不如尝试使用Java反编译实用程序。反编译器将尝试从指定文件创建源文件。 该