当前位置: 首页 > 知识库问答 >
问题:

java 8流的内部迭代看起来如何

方飞鸣
2023-03-14

我试图理解外部迭代器和内部迭代器的区别,外部迭代器使用迭代器来枚举它的元素

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());

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

共有1个答案

鲜于致远
2023-03-14

内部迭代的全部意义在于迭代逻辑可以以不同的方式实现。

< code>Iterable的默认实现如下所示

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

因此,当一个可迭代的,例如一个集合,没有为每个实现提供时,这个继承的实现与外部迭代相同。

重写此默认实现有不同的原因。例如,Collections.synchronized....(...)方法之一返回的包装器有一个如下所示的实现:

@Override
public void forEach(Consumer<? super E> consumer) {
    synchronized (mutex) {c.forEach(consumer);}
}

它委托原始集合的<code>forEach</code>实现执行它所做的任何操作,但在保持互斥的同时执行整个操作。换句话说,与外部迭代不同,外部迭代中代码必须关心自身的锁定,此实现免费提供同步。

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

@Override
public void forEach(Consumer<? super E> action) {
    c.forEach(action);
}

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

相比之下,内部迭代不是通过契约修改集合。因此包装器可以委托给原始集合的foreach方法,这将做正确的事情。如果目标方法根本使用迭代器,则不需要迭代器周围的包装器。

例如。这是ArrayListforeach方法:

@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();
    }
}

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

请注意,在可迭代对象(如集合上调用forEach,与在上调用forEach不同。流允许链接影响终端操作开始时将发生什么的各种中间操作。当您不链接中间操作,只调用<code>forEach(…)时,该调用可能执行与<code>集合等价的操作。spliterator().forEachRemaining(…)由流实现自行决定。

这最终与在集合上调用foreach的代码路径不同,但对于合理的集合实现,这些代码路径的作用基本相同。此答案比较了ArrayList的不同变体,虽然存在细微差异,但迭代的基本方式(使用数组中的索引)是相同的。

 类似资料:
  • 是否可以从迭代器创建一个流,其中对象的序列与通过反复调用迭代器的next()方法生成的序列相同?我所考虑的具体情况涉及到Treeset.desceningIterator()返回的迭代器的使用,但是我可以想象在其他情况下,迭代器是可用的,而不是它所引用的集合。 例如,对于,我们可以编写并按照该集合的排序顺序获取该集合中的对象流,但是如果我们希望它们按照不同的顺序,比如通过使用获得的顺序呢?我想象的

  • 我在迭代一个对象内的列表时遇到了一个问题,该对象内嵌在另一个映射中。我的目标是迭代这个列表并生成一个映射 ,我希望使用streams和lamdas来实现这一点。 我在上面看到了,我需要通过迭代FolderBo中的elementList从elementBo创建一个带有map 的映射。folderBo本身就在Modelbo的地图内。

  • 替代的解决方法可以是将有限的函数转换为对检查的异常更友好的普通旧foreach循环。 但天真的方法是失败的: 更新 回答这个问题的一个技巧以前在Why does stream not implementing iterable 上发布过?作为旁侧答案并不能真正回答问题本身。我认为这不足以将这个问题定性为那个问题的重复,因为他们问的是不同的问题。

  • 问题内容: 我只是想知道内部和外部迭代的真正好处是什么,以及为什么使用内部操作更好(至少是我所听到的)。在对集合进行内部迭代时,是否还可以删除集合的元素?就像在代码示例中一样: 我知道内部迭代的代码可读性更好,但是还有其他一些好处,例如性能改进? 问题答案: 您的情况有些简单,因为您可以简单地使用resp。而是使用内部迭代的替代方法,它也可以处理更复杂的条件。 在的情况下,这将立即显示内部迭代的优

  • 我有一个映射,它包含日期作为键和(另一个字符串作为键和列表作为值的映射)作为值。,我想填充另一个将String作为键和double作为值的映射。FlexiServer的内容包括 因此,基本上,我想首先迭代外部映射以获得内部映射,然后迭代内部映射以获得FlexiServers列表,并填充新的映射,其中服务器号作为键,数量列表作为值。我如何使用Java8流来实现这一点呢? 我尝试使用for循环,但我希

  • 我想单独打印ArrayList中的每个值,即{1=[A,B,C,D],2=[E,F,G,H]} 如果用户输入2(即键),则输出应为E F G H