将多个集合组合成一个逻辑集合?

Posted

技术标签:

【中文标题】将多个集合组合成一个逻辑集合?【英文标题】:Combine multiple Collections into a single logical Collection? 【发布时间】:2011-06-21 06:23:04 【问题描述】:

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

【问题讨论】:

^^ 链接断开。我想他指的是this method in the Guava Javadoc 【参考方案1】:

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

来自Iterables.concat( .. )JavaDocs:

将多个可迭代对象组合成一个 单个可迭代。返回的可迭代 有一个遍历 输入中每个可迭代的元素。 不轮询输入迭代器 直到必要。返回的 iterable 的迭代器支持remove() 当对应的输入迭代器 支持。

虽然这没有明确说明这是一个实时视图,但最后一句暗示它是(仅当支持迭代器支持 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 的请求,这里有一个类似的方法,它返回一个实时的 Collection View

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 更好的方法? @jn_ 只需将其包裹在 Iterables.unmodifiableIterable(iterable) 收藏呢? Iterables.concat 产生 Iterable,而不是 Collection。我需要Collection 视图。 @Damian 唯一有用的功能是拥有一个聚合的 size() 方法。 Collection 接口中的所有其他方法要么具有未定义的语义(添加等),要么具有糟糕的性能(包含等)。 @Sean,是的 - size() 是我需要的。 add() 抛出异常很好——我不关心这个方法。 Collections API 已损坏,没有人可以对此做任何事情。 Collection.add()Iterator.remove(),等等。【参考方案2】:

使用 Stream 的普通 Java 8 解决方案。

常数

假设private Collection&lt;T&gt; 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&lt;Collection&lt;T&gt;&gt; cs

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

【讨论】:

【参考方案3】:

如果您至少使用 Java 8,请参阅 my other answer。

如果您已经在使用 Google Guava,请参阅 Sean Patrick Floyd's answer。

如果您卡在 Java 7 并且不想包含 Google Guava,您可以编写自己的(只读)Iterables.concat(),使用不超过 IterableIterator

常数

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

【讨论】:

【参考方案4】:

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

【讨论】:

这将创建一个可能相当昂贵的新临时集合 昂贵的如何,列表中的底层对象不会被复制,ArrayList 只是分配空间并在后台调用System.arraycopy()。没有比这更高效的了。 如何为每次迭代复制整个集合昂贵?此外,您可以做得更好,请参阅 Seans 的回答。 它也使用原生实现来复制内存,它不会遍历数组。 好吧,如果它正在复制数组,那肯定是一个 O(n) 算法,它 缩放并且具有与遍历数组一次相同的复杂性。假设每个列表包含一百万个元素,那么我需要复制几百万个元素,只是为了迭代它们。坏主意。【参考方案5】:

这是我的解决方案:

编辑 - 稍微更改代码

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 你重新发明了org.apache.commons.collections4.IterableUtils.chainedIterable()

以上是关于将多个集合组合成一个逻辑集合?的主要内容,如果未能解决你的问题,请参考以下文章

如何将两个firebase集合组合成一个新的对象数组

将两个数组 排列组合到一个数组集合 求java 代码

对复合集合进行排序

TERSUS画画一样开发软件 集合类元件介绍-多个对象处理元件2

集合API

Solr 集合与核心