将多个可迭代对象包装成一个可迭代对象

3

假设我有两个Collections

Collection< Integer > foo = new ArrayList< Integer >();
Collection< Integer > bar = new ArrayList< Integer >();

有时我希望单独迭代它们,但有时也想一起迭代。是否有一种方法可以创建一个 foobar 的包装器,以便我可以迭代组合的对,但同时也在 foobar 更改时进行更新?(即Collection.addAll() 不适用)。

例如:

Collection< Integer > wrapper = ... // holds references to both bar and foo

foo.add( 1 );
bar.add( 99 );

for( Integer fooInt : foo ) {
    System.out.println( fooInt );
} // output: 1

for( Integer barInt : bar ) {
    System.out.println( barInt );
} // output: 99

for( Integer wrapInt : wrapper ) {
    System.out.println( wrapInt );
} // output: 1, 99

foo.add( 543 );

for( Integer wrapInt : wrapper ) {
    System.out.println( wrapInt );
} // output: 1, 99, 543

谢谢!


忘了说了,很遗憾我无法访问除了标准Java库以外的任何其他库。 - Matt Dunn
5个回答

5

我喜欢它的外观,但不幸的是对我来说不是一个选择:我无法使用除已有的库之外的任何库。 - Matt Dunn
@Dunnie:太糟糕了。你可以查看代码,看看如何编写自己的代码。这与@barjak所建议的非常相似。 - ColinD
能否解释一下为什么要给我点踩?在提到库的事情之前,我已经回答过这个问题了,而且这仍然可能是自己做这种事情的最佳参考。 - ColinD

3
我为此编写了两个函数:

/**
 * Create an Iterator from multiple Iterators. The returned Iterator
 * traverses all elements from all sources, in the order, as if they belong
 * to the same source.
 * 
 * @param <T> type of elements
 * @param sources sources of the elements, in order of traversal
 * @return an iterator over multiple iterators in sequence
 */
public static <T> Iterator<T> concatenate(final Iterator<T> ... sources) {
    if (sources.length == 0) {
        return new Iterator<T>() {
            @Override public boolean hasNext() { return false; }
            @Override public T next() { throw new NoSuchElementException("end of iteration"); }
            @Override public void remove() { throw new IllegalStateException("no previous element"); }
        };
    }
    return new Iterator<T>() {

        Iterator<Iterator<T>> sourcesIterator = Arrays.asList(sources).iterator();
        Iterator<T> currentIterator = sourcesIterator.next();

        @Override
        public boolean hasNext() {
            if (currentIterator.hasNext()) {
                return true;
            } else {
                if (sourcesIterator.hasNext()) {
                    currentIterator = sourcesIterator.next();
                    return hasNext();
                } else {
                    return false;
                }
            }
        }

        @Override
        public T next() {
            if (hasNext()) {
                return currentIterator.next();
            } else {
                throw new NoSuchElementException("end of iteration");
            }
        }

        @Override
        public void remove() {
            currentIterator.remove();
        }
    };
}

/**
 * Create an Iterable from multiple Iterables. The returned Iterable
 * traverses all elements from all sources, in the order, as if they belong
 * to the same source.
 * 
 * @param <T> type of elements
 * @param sources sources of the elements, in order of traversal
 * @return an iterable over multiple iterators in sequence
 */
@SuppressWarnings("unchecked") // impossible to create a generic array
public static <T> Iterable<T> concatenate(final Iterable<T> ... sources) {
    return new Iterable<T>() {
        @Override
        public Iterator<T> iterator() {
            final Iterator[] iteratorsArrays = new Iterator[sources.length];
            for (int i = 0; i < sources.length; i++) {
                iteratorsArrays[i] = sources[i].iterator();
            }
            return concatenate(iteratorsArrays);
        }
    };
}

1
我认为与Guava的方法相比,这种方法的主要问题在于当在连接的Iterable上调用iterator()时,它会急切地检索源迭代器的所有迭代器,而Guava仅在实际需要时检索每个迭代器。 我想在大多数情况下这不会是太大的问题,但它确实延长了迭代器的生命周期。 - ColinD
@ColinD:你说得对。这是设计上的问题,但显然有些情况下需要惰性创建迭代器。 - barjak

0
一个简单的列表嵌套并不需要太多样板代码:
List <List <Integer>> metalist = new ArrayList <List <Integer>> ();
metalist.add (foo);
metalist.add (bar);
for (List<Integer> list : metalist) 
    for (Integer wrapInt : list)
        System.out.println (wrapInt);

如果你现在将4加到foo中foo.add(4);,你只是重复了这两个循环。问题出在哪里?


0

关于ColinD的回答:除了我已经拥有的库之外,我无法使用其他任何库。 - Matt Dunn
1
啊,你得喜欢那些让你重新发明轮子的公司。 :-) - cjstehno

-1

像 @barjak 一样,但更短

public static <E> Collection<E> concat(Collection<E> ... es) {
    List<E> ret = new ArrayList<E>();
    for (Collection<E> e : es) ret.addAll(e);
    return ret;
}

public static <E> Iterable<E> viewOf(final Collection<E> ... es) {
    return new Iterable<E>() {
        public Iterator<E> iterator() {
            return concat(es).iterator();
        }
    };
}

Collection< Integer > foo = new ArrayList< Integer >();
Collection< Integer > bar = new ArrayList< Integer >();

// call as often as you like.
for(Integer i : concat(foo, bar))

// OR
Iterable<Integer> view = viewOf(foo, bar);

// call as often as you like.
for(Integer i : view)

这并没有完成所要求的功能。结果需要是底层可迭代对象的视图。 - ColinD
@ColinD,现在可以了。这取决于它是如何被称呼的。在我看来,这个差别是语义上的。 - Peter Lawrey
这更接近了,但仍然进行了不必要的复制,浪费时间和内存。 - ColinD
@ColinD,也许在实际应用中可以测量得到更少,但是我可以衡量开发人员的时间浪费和可能引入错误的风险。 ;) - Peter Lawrey
1
如果性能是一个真正的问题,你不会使用List<Integer>,而是使用像TIntArrayList或普通int[]这样的东西。 - Peter Lawrey
这是一个通用的方法,不仅适用于Integer。我想那只是一个例子。你不应该故意牺牲性能,只因为你没有使用最先进的高性能结构。数组和集合之间的可用性差异非常大。我同意你关于开发人员时间的看法(这就是为什么我建议使用库),但复制/粘贴这段代码与@barjak的代码相比真的没有任何优势。 - ColinD

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