将多个集合合并为一个逻辑集合?


110

假设我有恒定数量的集合(例如3个ArrayLists)作为类的成员。现在,我想将所有元素公开给其他类,以便它们可以简单地遍历所有元素(理想情况下为只读)。我正在使用番石榴集合,我想知道如何使用番石榴可迭代器/迭代器在内部集合上生成逻辑视图而无需制作临时副本。


^^链接断开。我认为他在Guava Javadoc中
RustyTheBoyRobot '16

Answers:


113

使用Guava,您可以使用Iterables.concat(Iterable<T> ...),它创建所有可迭代对象的实时视图,并串联在一起(如果更改可迭代对象,则串联版本也会更改)。然后将串联的可迭代对象包装为Iterables.unmodifiableIterable(Iterable<T>)(我之前没有看到只读要求)。

Iterables.concat( .. )JavaDocs:

将多个可迭代对象合并为一个可迭代对象。返回的Iterable具有一个迭代器,该迭代器遍历输入中每个Iterable的元素。直到必要时才轮询输入迭代器。remove() 当相应的输入迭代器支持它时,返回的iterable的迭代器也将支持它。

虽然这并未明确表示这是实时视图,但最后一句话暗示它是(Iterator.remove()仅在支持后备迭代器支持的情况下才支持该方法,除非使用实时视图)

样例代码:

final List<Integer> first  = Lists.newArrayList(1, 2, 3);
final List<Integer> second = Lists.newArrayList(4, 5, 6);
final List<Integer> third  = Lists.newArrayList(7, 8, 9);
final Iterable<Integer> all =
    Iterables.unmodifiableIterable(
        Iterables.concat(first, second, third));
System.out.println(all);
third.add(9999999);
System.out.println(all);

输出:

[1、2、3、4、5、6、7、8、9]
[1、2、3、4、5、6、7、8、9、9999999]


编辑:

根据Damian的要求,这是一种类似的方法,可返回实时的“收藏夹视图”

public final class CollectionsX {

    static class JoinedCollectionView<E> implements Collection<E> {

        private final Collection<? extends E>[] items;

        public JoinedCollectionView(final Collection<? extends E>[] items) {
            this.items = items;
        }

        @Override
        public boolean addAll(final Collection<? extends E> c) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void clear() {
            for (final Collection<? extends E> coll : items) {
                coll.clear();
            }
        }

        @Override
        public boolean contains(final Object o) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean containsAll(final Collection<?> c) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean isEmpty() {
            return !iterator().hasNext();
        }

        @Override
        public Iterator<E> iterator() {
            return Iterables.concat(items).iterator();
        }

        @Override
        public boolean remove(final Object o) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean removeAll(final Collection<?> c) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean retainAll(final Collection<?> c) {
            throw new UnsupportedOperationException();
        }

        @Override
        public int size() {
            int ct = 0;
            for (final Collection<? extends E> coll : items) {
                ct += coll.size();
            }
            return ct;
        }

        @Override
        public Object[] toArray() {
            throw new UnsupportedOperationException();
        }

        @Override
        public <T> T[] toArray(T[] a) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean add(E e) {
            throw new UnsupportedOperationException();
        }

    }

    /**
     * Returns a live aggregated collection view of the collections passed in.
     * <p>
     * All methods except {@link Collection#size()}, {@link Collection#clear()},
     * {@link Collection#isEmpty()} and {@link Iterable#iterator()}
     *  throw {@link UnsupportedOperationException} in the returned Collection.
     * <p>
     * None of the above methods is thread safe (nor would there be an easy way
     * of making them).
     */
    public static <T> Collection<T> combine(
        final Collection<? extends T>... items) {
        return new JoinedCollectionView<T>(items);
    }

    private CollectionsX() {
    }

}

如何防止用户删除元素?有没有比将列表包装到unmodifiableLists中更好的方法?
newgre 2011年


2
那收藏呢?Iterables.concat产生一个Iterable,不是Collection。我需要一个Collection视图。
Nowaker

@Damian唯一有用的功能是具有聚集的size()方法。Collection接口中的所有其他方法要么具有未定义的语义(添加等),要么具有较差的性能(包含等)。
肖恩·帕特里克·弗洛伊德

2
@Sean,是的-这size()是我需要的。add()抛出异常是好的-我不在乎这种方法。Collections API已损坏,没有人可以做任何事情。Collection.add()Iterator.remove(),等等。
Nowaker

101

使用Java的纯Java 8解决方案Stream

常数

假设private Collection<T> c, c2, c3

一种解决方案:

public Stream<T> stream() {
    return Stream.concat(Stream.concat(c.stream(), c2.stream()), c3.stream());
}

另一个解决方案:

public Stream<T> stream() {
    return Stream.of(c, c2, c3).flatMap(Collection::stream);
}

可变数

假设private Collection<Collection<T>> cs

public Stream<T> stream() {
    return cs.stream().flatMap(Collection::stream);
}

10

如果您至少使用Java 8,请参阅我的其他答案

如果您已经在使用Google Guava,请参阅Sean Patrick Floyd的答案

如果您坚持使用Java 7,并且不想包含Google Guava,则可以Iterables.concat()使用Iterable和最多编写自己的(只读)代码Iterator

常数

public static <E> Iterable<E> concat(final Iterable<? extends E> iterable1,
                                     final Iterable<? extends E> iterable2) {
    return new Iterable<E>() {
        @Override
        public Iterator<E> iterator() {
            return new Iterator<E>() {
                final Iterator<? extends E> iterator1 = iterable1.iterator();
                final Iterator<? extends E> iterator2 = iterable2.iterator();

                @Override
                public boolean hasNext() {
                    return iterator1.hasNext() || iterator2.hasNext();
                }

                @Override
                public E next() {
                    return iterator1.hasNext() ? iterator1.next() : iterator2.next();
                }
            };
        }
    };
}

可变数

@SafeVarargs
public static <E> Iterable<E> concat(final Iterable<? extends E>... iterables) {
    return concat(Arrays.asList(iterables));
}

public static <E> Iterable<E> concat(final Iterable<Iterable<? extends E>> iterables) {
    return new Iterable<E>() {
        final Iterator<Iterable<? extends E>> iterablesIterator = iterables.iterator();

        @Override
        public Iterator<E> iterator() {
            return !iterablesIterator.hasNext() ? Collections.emptyIterator()
                                                : new Iterator<E>() {
                Iterator<? extends E> iterableIterator = nextIterator();

                @Override
                public boolean hasNext() {
                    return iterableIterator.hasNext();
                }

                @Override
                public E next() {
                    final E next = iterableIterator.next();
                    findNext();
                    return next;
                }

                Iterator<? extends E> nextIterator() {
                    return iterablesIterator.next().iterator();
                }

                Iterator<E> findNext() {
                    while (!iterableIterator.hasNext()) {
                        if (!iterablesIterator.hasNext()) {
                            break;
                        }
                        iterableIterator = nextIterator();
                    }
                    return this;
                }
            }.findNext();
        }
    };
}

0

您可以创建一个新的ListaddAll()其他List的。然后使用返回不可修改的列表Collections.unmodifiableList()


3
这将创建一个可能非常昂贵的新临时集合
newgre 2011年

5
昂贵的方式,列表中的基础对象不会被复制,而ArrayList只是在后台分配空间和调用System.arraycopy()。没有比这更有效率的了。
Qwerky 2011年

8
如何为每次迭代复制整个集合并不昂贵?而且,您可以得到比这更好的结果,请参阅Seans的答案。
newgre 2011年

它还使用本机实现来复制内存,它不会遍历数组。
2011年

1
好吧,如果要复制数组,那肯定是O(n)算法,它无法缩放,并且具有与遍历数组一次相同的复杂度。假设每个列表包含一百万个元素,那么我需要复制几百万个元素,仅对其进行迭代。馊主意。
newgre 2011年

0

这是我的解决方案:

编辑-更改了一点代码

public static <E> Iterable<E> concat(final Iterable<? extends E> list1, Iterable<? extends E> list2)
{
    return new Iterable<E>()
    {
        public Iterator<E> iterator()
        {
            return new Iterator<E>()
            {
                protected Iterator<? extends E> listIterator = list1.iterator();
                protected Boolean checkedHasNext;
                protected E nextValue;
                private boolean startTheSecond;

                public void theNext()
                {
                    if (listIterator.hasNext())
                    {
                        checkedHasNext = true;
                        nextValue = listIterator.next();
                    }
                    else if (startTheSecond)
                        checkedHasNext = false;
                    else
                    {
                        startTheSecond = true;
                        listIterator = list2.iterator();
                        theNext();
                    }
                }

                public boolean hasNext()
                {
                    if (checkedHasNext == null)
                        theNext();
                    return checkedHasNext;
                }

                public E next()
                {
                    if (!hasNext())
                        throw new NoSuchElementException();
                    checkedHasNext = null;
                    return nextValue;

                }

                public void remove()
                {
                    listIterator.remove();
                }
            };
        }
    };
}

你的实现翻转的角色hasNext()next()。第一个更改迭代器的状态,而第二个则不更改。相反。调用next(),而不调用hasNext()总是产生null。调用hasNext(),而不调用next()会扔掉元素。您next()也不抛出NoSuchElementException任何异常,而是返回null
xehpuk
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.