在Java中是否有可能合并迭代器?

54

在Java中是否有可能合并迭代器?我有两个迭代器,想要将它们组合/合并,以便可以在同一个循环中遍历它们的元素,而不是分别迭代两次。这可行吗?

请注意,这两个列表中的元素数量可能不同,因此一次循环遍历两个列表不是解决方案。

Iterator<User> pUsers = userService.getPrimaryUsersInGroup(group.getId());
Iterator<User> sUsers = userService.getSecondaryUsersInGroup(group.getId());

while(pUsers.hasNext()) {
  User user = pUsers.next();
  .....
}

while(sUsers.hasNext()) {
  User user = sUsers.next();
  .....
}
14个回答

55

4
@youssef @Colin: 这个链接的意图是立即跳转到 concat() 方法,而不需要滚动页面(使用 # 锚点)。然而,该部分没有正确进行URL编码。我已经修复了它(感谢 Firefox 自动对从地址栏复制粘贴的链接进行 URL 编码)。 - BalusC
我认为Iterators.concat对于小集合非常有用,但不适用于访问数据库的可迭代对象。concat方法首先将所有给定的迭代器转换为ArrayList,然后返回其迭代器。Commons Collections似乎使用更少的内存,因为它不会将其转换为ArrayList,而是保持两个迭代器不变,直到必要时才进行转换。请注意这种行为! - guerda
2
@guerda:你错了。Iterators.concat是惰性的;它不会在列表中缓存元素。 - David
@David:你确定吗?我是基于这个实现假设的:http://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/collect/Iterators.java?r=9dcde66ab9bfcdbe007b69d4912a297894bece59#524 - guerda
1
@guerda:是的。你提到的代码创建了一个迭代器数组;它与这些迭代器的内容无关。 - David
显示剩余4条评论

21

2
我认为这种方法的资源消耗较少,因为它不会将所有迭代器转换为ArrayList。 - guerda
Guava的javadoc目前明确提到,只有在必要时才会轮询连接的迭代器。 - TeamDman

19

您可以创建自己的Iterator接口实现,以便迭代迭代器:

public class IteratorOfIterators implements Iterator {
    private final List<Iterator> iterators;

    public IteratorOfIterators(List<Iterator> iterators) {
        this.iterators = iterators;
    }

    public IteratorOfIterators(Iterator... iterators) {
        this.iterators = Arrays.asList(iterators);
    }


    public boolean hasNext() { /* implementation */ }

    public Object next() { /* implementation */ }

    public void remove() { /* implementation */ }
}

(出于简洁考虑,我没有将泛型添加到迭代器中。) 实现并不太难,但也不是最简单的,你需要跟踪当前正在迭代的Iterator,并在调用next()时尽可能地遍历迭代器,直到找到返回truehasNext(),或者可能会到达最后一个迭代器的末尾。

我不知道是否已经存在此类实现。

更新:
我已经为Andrew Duffy的回答投票 - 没有必要重新发明轮子。 我真的需要更深入地研究Guava。

我为可变数量参数添加了另一个构造函数 - 几乎偏离了主题,因为这里构建类的方式并不是真正感兴趣的,而是它是如何工作的概念。


14

我有一段时间没有写Java代码了,这让我很好奇自己是否还能做到。

第一次尝试:

import java.util.Iterator;
import java.util.Arrays; /* For sample code */

public class IteratorIterator<T> implements Iterator<T> {
    private final Iterator<T> is[];
    private int current;

    public IteratorIterator(Iterator<T>... iterators)
    {
            is = iterators;
            current = 0;
    }

    public boolean hasNext() {
            while ( current < is.length && !is[current].hasNext() )
                    current++;

            return current < is.length;
    }

    public T next() {
            while ( current < is.length && !is[current].hasNext() )
                    current++;

            return is[current].next();
    }

    public void remove() { /* not implemented */ }

    /* Sample use */
    public static void main(String... args)
    {
            Iterator<Integer> a = Arrays.asList(1,2,3,4).iterator();
            Iterator<Integer> b = Arrays.asList(10,11,12).iterator();
            Iterator<Integer> c = Arrays.asList(99, 98, 97).iterator();

            Iterator<Integer> ii = new IteratorIterator<Integer>(a,b,c);

            while ( ii.hasNext() )
                    System.out.println(ii.next());
    }
}

您当然可以使用更多的集合类而不是纯数组+索引计数器,但实际上这比另一种方法更清晰。或者说我现在主要写C,所以有点偏见?
无论如何,答案是“是的,很可能”

8
public class IteratorJoin<T> implements Iterator<T> {
    private final Iterator<T> first, next;

    public IteratorJoin(Iterator<T> first, Iterator<T> next) {
        this.first = first;
        this.next = next;
    }

    @Override
    public boolean hasNext() {
        return first.hasNext() || next.hasNext();
    }

    @Override
    public T next() {
        if (first.hasNext())
            return first.next();
        return next.next();
    }
}

7
从Java 8开始,可以使用 Stream API来完成此操作,而无需外部依赖。这还允许将迭代器与其他类型的流进行连接。
Streams.concat(
   StreamSupport.stream(<iter1>, false), 
   StreamSupport.stream(<iter2>, false));

5

将循环移到一个方法中,并将迭代器传递给该方法。

void methodX(Iterator x) {
    while (x.hasNext()) {
        ....
    }
}

1
谢谢。但我仍然需要调用该方法两次。 - Jahanzeb Farooq
这对于你的特定情况似乎是最简单的解决方案(不使用Guava)。是的,你必须调用methodX两次,但无论如何你都必须进行两个方法调用,一个用于合并迭代器,另一个用于执行methodX所做的操作。你自己的标志解决方案似乎更加复杂,可能需要更多的代码。 - Alb

4

迭代器来源于集合或者数组。
为什么不使用已有的方法
Collection.addAll(Collection c);
然后从最后一个对象创建迭代器。
这样,你的迭代器将遍历两个集合的所有内容。


3
这样做有一些缺点,特别是当你想要使用惰性迭代器或集合非常大的时候。 - Fabian
有很多理由不使用这个。迭代器不一定要来自集合或集合。即使它们是,除非您知道将遍历所有引用,否则不应复制所有这些引用。 - Navin
它不一定来自一个集合,回到基础,迭代器是任何能够迭代的东西,不需要是一个集合,他们称之为抽象化。 - zakmck

3
你可以使用我的版本的可扩展迭代器。它使用一个双端队列的迭代器,这对我来说很有意义。
import java.util.Deque;
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedDeque;

public class ExtendableIterator<T> implements Iterator<T> {

    public Deque<Iterator<T>> its = new ConcurrentLinkedDeque<Iterator<T>>();

    public ExtendableIterator() {

    }

    public ExtendableIterator(Iterator<T> it) {
        this();
        this.extend(it);
    }

    @Override
    public boolean hasNext() {
        // this is true since we never hold empty iterators
        return !its.isEmpty() && its.peekLast().hasNext();
    }

    @Override
    public T next() {
        T next = its.peekFirst().next();
        if (!its.peekFirst().hasNext()) {
            its.removeFirst();
        }
        return next;
    }

    public void extend(Iterator<T> it) {
        if (it.hasNext()) {
            its.addLast(it);
        }
    }
}

2
合并迭代器:
import static java.util.Arrays.asList;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;


public class ConcatIterator<T> implements Iterator<T> {

    private final List<Iterable<T>> iterables;
    private Iterator<T> current;

    @SafeVarargs
    public ConcatIterator(final Iterable<T>... iterables) {
        this.iterables = new LinkedList<>(asList(iterables));
    }

    @Override
    public boolean hasNext() {
        checkNext();
        return current != null && current.hasNext();
    }

    @Override
    public T next() {
        checkNext();
        if (current == null || !current.hasNext()) throw new NoSuchElementException();
        return current.next();
    }

    @Override
    public void remove() {
        if (current == null) throw new IllegalStateException();
        current.remove();
    }

    private void checkNext() {
        while ((current == null || !current.hasNext()) && !iterables.isEmpty()) {
            current = iterables.remove(0).iterator();
        }
    }

}

使用concat方法创建一个Iterable

@SafeVarargs
public static <T> Iterable<T> concat(final Iterable<T>... iterables) {
    return () -> new ConcatIterator<>(iterables);
}

简单的JUnit测试:

@Test
public void testConcat() throws Exception {
    final Iterable<Integer> it1 = asList(1, 2, 3);
    final Iterable<Integer> it2 = asList(4, 5);
    int j = 1;
    for (final int i : concat(it1, it2)) {
        assertEquals(j, i);
        j++;
    }
}

为什么不直接使用Arrays.asList()返回的List,而要使用LinkedList呢?因为"iterables"数组可能会发生变化吗?即使没有多线程,这也是我需要担心的事情吗?我需要重新审查一些代码... - Nic Stray
@NicStray 这里的 LinkedList 仅用于创建一个防御性副本,以便迭代元素。这是一种设计选择。你可以直接使用 asList(..) 并避免复制操作,但在使用 Iterator 之前必须确保不修改原始可迭代对象。选择权在你手中... - benez
1
@NicStray 这里调用了 LinkedList 的复制构造函数。如果你想直接使用 asList(..),你需要有一个索引变量或者改变代码,因为删除操作将不再起作用。 - benez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接