java 8 个流的内部迭代情况
How Internal iteration looks like for java 8 streams
我试图了解外部迭代器与内部迭代器之间的区别,其中外部迭代器使用迭代器枚举其元素
List<String> alphabets = Arrays.asList(new String[]{"a","b","b","d"});
for(String letter: alphabets){
System.out.println(letter.toUpperCase());
}
以上代码在后台执行如下操作
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());
}
但是对于内部迭代,一切都在后台完成,这对我来说是一个黑盒子,我想深入研究它。
就像下面的代码一样,迭代在后台发生,但究竟发生了什么,与 foreach 循环相比有何不同?
List<String> alphabets = Arrays.asList(new String[]{"a","b","b","d"});
alphabets.stream().forEach(l -> l.toUpperCase());
这是我对外部迭代和内部迭代的理解。如有不妥请指正
内部迭代的全部意义在于可以用不同的方式实现迭代逻辑。
Iterable
的默认实现类似于
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
所以当一个可迭代的,例如集合,不提供 forEach
实现,此继承实现与外部迭代相同。
覆盖此默认实现的原因有多种。例如,Collections.synchronized…(…)
方法之一返回的包装器具有如下所示的实现:
@Override
public void forEach(Consumer<? super E> consumer) {
synchronized (mutex) {c.forEach(consumer);}
}
它委托原始集合的 forEach
实现,做它做的任何事情,但是在持有互斥量的同时执行整个操作。换句话说,与代码必须关心锁定自身的外部迭代不同,此实现免费提供同步。
另一个有趣的例子是在 Collections.unmodifiable…(…)
包装器中。
@Override
public void forEach(Consumer<? super E> action) {
c.forEach(action);
}
乍一看优势不明显。但是,正如您在问题中所展示的,外部迭代是使用 Iterator
完成的,即使在使用 for-each 语法时也是如此。 Iterator
有一个 remove
方法,因此每次对不可修改的包装器执行外部迭代时,原始集合的迭代器必须包装在另一个迭代器中,以防止调用者使用 remove()
.
相比之下,内部迭代不是按合同修改集合。所以包装器可以委托给原始集合的 forEach
方法来做正确的事情。如果目标方法完全使用迭代器,则不需要迭代器周围的包装器。
例如这是 ArrayList
的 forEach
方法:
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
因此,它不是基于迭代器的循环,而是遍历索引并直接访问其数组。
请注意,在 Collection
等可迭代对象上调用 forEach
不同于在 Stream
上调用 forEach
。流允许链接各种中间操作,影响终端操作开始时将发生的事情。当您不链接中间操作而只是调用 forEach(…)
时,该调用可能会根据流实现的判断执行等同于 collection.spliterator() .forEachRemaining(…)
的操作。
这与在集合上调用 forEach
的代码路径不同,但对于合理的集合实现,这些代码路径的作用基本相同。 比较了 ArrayList
的不同变体,虽然存在细微差别,但迭代(使用数组索引)的基本方法是相同的。
我试图了解外部迭代器与内部迭代器之间的区别,其中外部迭代器使用迭代器枚举其元素
List<String> alphabets = Arrays.asList(new String[]{"a","b","b","d"});
for(String letter: alphabets){
System.out.println(letter.toUpperCase());
}
以上代码在后台执行如下操作
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());
}
但是对于内部迭代,一切都在后台完成,这对我来说是一个黑盒子,我想深入研究它。
就像下面的代码一样,迭代在后台发生,但究竟发生了什么,与 foreach 循环相比有何不同?
List<String> alphabets = Arrays.asList(new String[]{"a","b","b","d"});
alphabets.stream().forEach(l -> l.toUpperCase());
这是我对外部迭代和内部迭代的理解。如有不妥请指正
内部迭代的全部意义在于可以用不同的方式实现迭代逻辑。
Iterable
的默认实现类似于
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
所以当一个可迭代的,例如集合,不提供 forEach
实现,此继承实现与外部迭代相同。
覆盖此默认实现的原因有多种。例如,Collections.synchronized…(…)
方法之一返回的包装器具有如下所示的实现:
@Override
public void forEach(Consumer<? super E> consumer) {
synchronized (mutex) {c.forEach(consumer);}
}
它委托原始集合的 forEach
实现,做它做的任何事情,但是在持有互斥量的同时执行整个操作。换句话说,与代码必须关心锁定自身的外部迭代不同,此实现免费提供同步。
另一个有趣的例子是在 Collections.unmodifiable…(…)
包装器中。
@Override
public void forEach(Consumer<? super E> action) {
c.forEach(action);
}
乍一看优势不明显。但是,正如您在问题中所展示的,外部迭代是使用 Iterator
完成的,即使在使用 for-each 语法时也是如此。 Iterator
有一个 remove
方法,因此每次对不可修改的包装器执行外部迭代时,原始集合的迭代器必须包装在另一个迭代器中,以防止调用者使用 remove()
.
相比之下,内部迭代不是按合同修改集合。所以包装器可以委托给原始集合的 forEach
方法来做正确的事情。如果目标方法完全使用迭代器,则不需要迭代器周围的包装器。
例如这是 ArrayList
的 forEach
方法:
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
因此,它不是基于迭代器的循环,而是遍历索引并直接访问其数组。
请注意,在 Collection
等可迭代对象上调用 forEach
不同于在 Stream
上调用 forEach
。流允许链接各种中间操作,影响终端操作开始时将发生的事情。当您不链接中间操作而只是调用 forEach(…)
时,该调用可能会根据流实现的判断执行等同于 collection.spliterator() .forEachRemaining(…)
的操作。
这与在集合上调用 forEach
的代码路径不同,但对于合理的集合实现,这些代码路径的作用基本相同。 ArrayList
的不同变体,虽然存在细微差别,但迭代(使用数组索引)的基本方法是相同的。