如何以惰性方式连接 java 中的两个集合?

How to concatenate two collections in java in a lazy way?

我有两个集合,我想 return 一个 IEnumerable,它是它们的串联。 returned 枚举应该是惰性的,不应该修改两个初始集合(所以,我不想将两个集合复制到一个集合然后 return 结果,因为那不是惰性的)

下面的代码是我想在 Java 中实现但用 c# 编写的示例:

public static IEnumerable<int> all()
{
    List<int> list1 = new List<int>() { 1, 2, 3 };
    List<int> list2 = new List<int>() { 4, 5, 6 };
    return list1.Concat(list2);
}

您可以使用 Apache Commons Collections method IterableUtils.chainedIterable(list1, list2):

Combines the provided iterables into a single iterable.

The returned iterable has an iterator that traverses the elements in the order of the arguments, i.e. iterables[0], iterables2, .... The source iterators are not polled until necessary.

Guava method Iterables.concat(list1, list2):

Combines multiple iterables into a single iterable. The returned iterable has an iterator that traverses the elements of each iterable in inputs. The input iterators are not polled until necessary.

可以在 Commons Collections: <E> Iterable<E> IterableUtils.chainedIterable(Iterable<? extends E> a, Iterable<? extends E> b) 中找到 C# IEnumerable<TSource> Enumerable.Concat<TSource>(IEnumerable<TSource>, IEnumerable‌​<TSource>) 的 Java 等价物。查一下。

这是一个返回 Iterator.

的手动实现
public static <T> Iterator<T> concatIterator(Iterable<T> l1, Iterable<T> l2) {
    return new Iterator<>() {
        Iterator<T> it1 = l1.iterator();
        Iterator<T> it2 = l2.iterator();
        public boolean hasNext() {
            return it1.hasNext() || it2.hasNext();
        }
        public T next() {
            return it1.hasNext() ? it1.next() : it2.next();
        }
    }
}

你可以用它

得到一个Iterable<T>
public static <T> Iterable<T> concatIterable(Iterable<T> l1, Iterable<T> l2) {
    return new Iterable<>() {
        public Iterator<T> iterator() {
            return concatIterator(l1, l2);
        }
    }
}

如果您只需要 Iterable,那么简单的 Chain 应该适合您。

class Chain<T> implements Iterable<T> {
    final Iterable<Iterable<T>> lists;

    public Chain(Iterable<T>... lists) {
        this.lists = Arrays.asList(lists);
    }

    @Override
    public Iterator<T> iterator() {
        return new Iterator<T>() {
            // Walks the lists.
            Iterator<Iterable<T>> i = lists.iterator();
            // Walks the list.
            Iterator<T> l = prime(i.hasNext() ? i.next().iterator() : null);

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

            @Override
            public T next() {
                if (hasNext()) {
                    T next = l.next();
                    l = prime(l);
                    return next;
                } else {
                    throw new NoSuchElementException("Chain exhausted.");
                }
            }

            private Iterator<T> prime(Iterator<T> l) {
                // Prepare for next time.
                while (l != null && !l.hasNext()) {
                    if (i.hasNext()) {
                        l = i.next().iterator();
                    } else {
                        l = null;
                    }
                }
                return l;
            }
        };
    }
}

public void test(String[] args) {
    Chain<Integer> chain = new Chain<>(
            Arrays.asList(),
            Arrays.asList(1, 2, 3),
            Arrays.asList(),
            Arrays.asList(4, 5, 6),
            Arrays.asList(7,8,9,10),
            Arrays.asList()
            );
    for (Integer i : chain) {
        System.out.println(i);
    }
}

您也可以为 List<T> 甚至 Collection<T> 做类似的工作。

如果您的输入是列表,那么 ChainedList 下面的 class 可以将它们换行。

我不得不将两个列表作为一个惰性列表提供给 JAXB 编组器,但它不适用于 Iterable。要使用 maxOccurs="unbounded" 创建元素,JAXB 需要 bean 属性 至少实现 java.util.Collection.

该解决方案使用 Apache Commons,但在 Guava 中有一个等效的 Iterables.concat()

Up-to-date version & unit test

import org.apache.commons.collections4.IterableUtils;

/** This class makes multiple lists look like one to the caller. */
public class /* NOSONAR */ ChainedList<E> extends AbstractList<E> implements RandomAccess {

    private final List<E>[] elements;

    private final int[] endIndexes;

    @SafeVarargs
    public ChainedList(final List<E>... lists) {
        this(Arrays.asList(lists));
    }

    @SuppressWarnings("unchecked")
    public ChainedList(final Iterable<List<E>> elements) {
        final List<List<E>> tmpElementsList = IterableUtils.toList(elements);
        this.elements = tmpElementsList.toArray(new List[tmpElementsList.size()]);
        endIndexes = new int[this.elements.length];
        int currentSize = 0;
        for (int i = 0; i < this.elements.length; i++) {
            final List<E> curr = this.elements[i];
            final int sz = curr.size();
            endIndexes[i] = currentSize + sz;
            currentSize += sz;
        }
    }

    @Override
    public E get(final int index) {
        final int partitionIndex = getPartitionIndex(index);
        final int subIndex = getSubIndex(partitionIndex, index);

        // throws when index >= size()
        final List<E> subList = elements[partitionIndex];
        // throws when index is negative or last sublist is empty
        return subList.get(subIndex);
    }

    @Override
    public E set(final int index, final E element) {
        final int partitionIndex = getPartitionIndex(index);
        final int subIndex = getSubIndex(partitionIndex, index);

        // throws when index >= size()
        final List<E> subList = elements[partitionIndex];
        if (subIndex < 0 || subIndex >= subList.size()) {
            // a sublist may throw unsupported even when index OOB
            throw new IndexOutOfBoundsException();
        }
        return subList.set(subIndex, element);
    }

    @Override
    public Iterator<E> iterator() {
        // this may perform better with contained LinkedList
        return IterableUtils.chainedIterable(elements).iterator();
    }

    @Override
    public ListIterator<E> listIterator(final int index) {
        // indexOf, lastIndexOf, equals, removeRange, subList
        // call this method and for non-RandomAccess lists
        // the default implementation is slow
        // T O D O: implement LazyListIteratorChain similar to
        // org.apache.commons.collections4.iterators.LazyIteratorChain
        for (final List<E> subList : elements) {
            if (!(subList instanceof RandomAccess)) {
                throw new UnsupportedOperationException(
                    "Not RandomAccess: " + subList.getClass().getName());
            }
        }
        return super.listIterator(index);
    }

    /**
     * @return negative value when {@code index} is negative
     */
    private int getSubIndex(final int partitionIndex, final int index) {
        return index - (partitionIndex == 0 ? 0 : endIndexes[partitionIndex - 1]);
    }

    private int getPartitionIndex(final int index) {
        int location = Arrays.binarySearch(endIndexes, index);
        if (location < 0) {
            location = (~location) - 1;
        }
        return location + 1;
    }

    @Override
    public int size() {
        return endIndexes.length == 0 ? 0 : endIndexes[endIndexes.length - 1];
    }
}