java 8 流的内部迭代是什么样子的

Dan*_*Dan 0 java java-8 java-stream

我试图理解外部迭代器与内部迭代器之间的区别,其中外部迭代器使用迭代器来枚举其元素

List<String> alphabets = Arrays.asList(new String[]{"a","b","b","d"});
         
for(String letter: alphabets){
   System.out.println(letter.toUpperCase());
}
Run Code Online (Sandbox Code Playgroud)

上面的后台代码做了类似下面的事情

List<String> alphabets = Arrays.asList(new String[]{"a","b","b","d"});    
Iterator<String> iterator = alphabets.listIterator();
while(iterator.hasNext()){
     System.out.println(iterator.next().toUpperCase());
}
Run Code Online (Sandbox Code Playgroud)

但对于内部迭代,一切都是在后台完成的,这对我来说是一个黑匣子,我想深入研究它。

就像下面的代码一样,迭代是在后台发生的,但到底发生了什么以及与 foreach 循环相比有何不同?

List<String> alphabets = Arrays.asList(new String[]{"a","b","b","d"});
alphabets.stream().forEach(l -> l.toUpperCase());
Run Code Online (Sandbox Code Playgroud)

这是我对外部迭代和内部迭代的理解。如果我错了,请纠正我。

Hol*_*ger 7

内部迭代的要点在于迭代逻辑可以以不同的方式实现。

\n

默认实现Iterable看起来像

\n
default void forEach(Consumer<? super T> action) {\n    Objects.requireNonNull(action);\n    for (T t : this) {\n        action.accept(t);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

因此,当一个可迭代对象(例如集合)不提供实现时forEach,该继承的实现与外部迭代的作用相同。

\n

有不同的原因需要覆盖此默认实现。例如,其中一种Collections.synchronized\xe2\x80\xa6(\xe2\x80\xa6)方法返回的包装器具有如下所示的实现:

\n
@Override\npublic void forEach(Consumer<? super E> consumer) {\n    synchronized (mutex) {c.forEach(consumer);}\n}\n
Run Code Online (Sandbox Code Playgroud)\n

它委托给原始 collection\xe2\x80\x99sforEach实现来执行它所做的任何操作,但在持有互斥体的同时执行整个操作。换句话说,与代码必须关心自身锁定的外部迭代不同,此实现免费提供同步。

\n

另一个有趣的例子是包装Collections.unmodifiable\xe2\x80\xa6(\xe2\x80\xa6)器。

\n
@Override\npublic void forEach(Consumer<? super E> action) {\n    c.forEach(action);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

乍一看优势并不明显。但是,正如您在问题中所示的\xe2\x80\x99ve 所示,Iterator即使使用 for-each 语法,外部迭代也是使用 完成的。AnIterator有一个remove方法,因此每次对不可修改的包装器执行外部迭代时,原始的 collection\xe2\x80\x99s 迭代器必须包装在另一个迭代器中,以防止调用者使用remove().

\n

相反,内部迭代不会通过契约修改集合。因此包装器可以委托给原始集合\xe2\x80\x99sforEach方法,该方法将执行正确的操作。如果目标方法根本使用迭代器,则不需要迭代器周围的包装器。

\n

例如,这是ArrayList\xe2\x80\x99sforEach方法:

\n
@Override\npublic void forEach(Consumer<? super E> action) {\n    Objects.requireNonNull(action);\n    final int expectedModCount = modCount;\n    @SuppressWarnings("unchecked")\n    final E[] elementData = (E[]) this.elementData;\n    final int size = this.size;\n    for (int i=0; modCount == expectedModCount && i < size; i++) {\n        action.accept(elementData[i]);\n    }\n    if (modCount != expectedModCount) {\n        throw new ConcurrentModificationException();\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

因此,它不是基于迭代器的循环,而是迭代索引并直接访问其数组。

\n

请注意,调用forEach像 a 这样的可迭代对象与调用aCollection不同。流允许链接各种中间操作,影响终端操作开始时将发生的情况。当您不链接中间操作而仅调用 时,该调用可能会执行与流实现 xe2x80x99相同的操作。forEachStreamforEach(\xe2\x80\xa6)collection.spliterator() .forEachRemaining(\xe2\x80\xa6)

\n

这最终会产生与调用集合不同的代码路径forEach,但对于合理的集合实现,这些代码路径将执行基本相同的操作。这个答案比较了不同的变体,ArrayList虽然存在细微的差异,但迭代的基本方法(使用数组的索引)是相同的。

\n