120

Assume, I have a constant number of collections (e.g. 3 ArrayLists) as members of a class. Now, I want to expose all the elements to other classes so they can simply iterate over all elements (ideally, read only). I'm using guava collections and I wonder how I could use guava iterables/iterators to generate a logical view on the internal collections without making temporary copies.

newgre
  • 5,245
  • 4
  • 31
  • 41
  • ^^ Broken link. I think he was pointing to [this method in the Guava Javadoc](https://google.github.io/guava/releases/snapshot/api/docs/com/google/common/collect/Iterables.html#concat(java.lang.Iterable,%20java.lang.Iterable,%20java.lang.Iterable)) – RustyTheBoyRobot Oct 07 '16 at 22:01

5 Answers5

123

With Guava, you can use Iterables.concat(Iterable<T> ...), it creates a live view of all the iterables, concatenated into one (if you change the iterables, the concatenated version also changes). Then wrap the concatenated iterable with Iterables.unmodifiableIterable(Iterable<T>) (I hadn't seen the read-only requirement earlier).

From the Iterables.concat( .. ) JavaDocs:

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. The returned iterable's iterator supports remove() when the corresponding input iterator supports it.

While this doesn't explicitly say that this is a live view, the last sentence implies that it is (supporting the Iterator.remove() method only if the backing iterator supports it is not possible unless using a live view)

Sample Code:

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

Output:

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


Edit:

By Request from Damian, here's a similar method that returns a live 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() {
    }

}
Grzegorz Rożniecki
  • 27,415
  • 11
  • 90
  • 112
Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • How would I prevent the user from removing elements? Is there a nicer way than wrapping the lists into unmodifiableLists? – newgre Feb 04 '11 at 10:35
  • 3
    @jn_ just wrap it in [`Iterables.unmodifiableIterable(iterable)`](http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/collect/Iterables.html#unmodifiableIterable%28java.lang.Iterable%29) – Sean Patrick Floyd Feb 04 '11 at 10:36
  • 2
    What about collections? `Iterables.concat` produces an `Iterable`, not `Collection`. I would need a `Collection` view. – Nowaker Aug 02 '11 at 10:39
  • @Damian the only useful feature of that would be to have an aggregated size() method. All other methods in the Collection interface would either have undefined semantics (add etc) or miserable performance (contains etc). – Sean Patrick Floyd Aug 02 '11 at 13:00
  • But you can of course use the Iterable created by Iterables.concat to populate a Collection using one of the Guava factory methods in Sets or Lists, but that would not be a live view. – Sean Patrick Floyd Aug 02 '11 at 13:01
  • 2
    @Sean, yes - `size()` is what I need. `add()` throwing an exception is good - I don't care about this method. Collections API is broken and noone can do anything about it. `Collection.add()`, `Iterator.remove()`, blah. – Nowaker Aug 04 '11 at 12:36
  • The `Collection` variant is awesome! Exactly what I needed. I'm confused why this class isn't commonly available in Java API or Guava :( http://stackoverflow.com/questions/6416706/easy-way-to-change-iterable-into-collection#comment36189718_6416800 – Hendy Irawan May 10 '14 at 11:58
  • @Sean Thanks! Added to Soluvas Framework: https://github.com/soluvas/soluvas-framework/commit/34b22c3f20b9d55f061d2ef8c539efb995af15d6 – Hendy Irawan May 10 '14 at 12:08
105

Plain Java 8 solutions using a Stream.

Constant number

Assuming private Collection<T> c, c2, c3.

One solution:

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

Another solution:

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

Variable number

Assuming private Collection<Collection<T>> cs:

public Stream<T> stream() {
    return cs.stream().flatMap(Collection::stream);
}
Caleb Jares
  • 6,163
  • 6
  • 56
  • 83
xehpuk
  • 7,814
  • 3
  • 30
  • 54
11

If you're using at least Java 8, see my other answer.

If you're already using Google Guava, see Sean Patrick Floyd's answer.

If you're stuck at Java 7 and don't want to include Google Guava, you can write your own (read-only) Iterables.concat() using no more than Iterable and Iterator:

Constant number

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

Variable number

@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();
        }
    };
}
xehpuk
  • 7,814
  • 3
  • 30
  • 54
2

You could create a new List and addAll() of your other Lists to it. Then return an unmodifiable list with Collections.unmodifiableList().

disco crazy
  • 31,313
  • 12
  • 80
  • 83
Qwerky
  • 18,217
  • 6
  • 44
  • 80
  • 3
    That would create a new temporary collection which is potentially quite expensive – newgre Feb 04 '11 at 10:13
  • 7
    Expensive *how*, the underlying objects in the lists aren't copied and `ArrayList` just allocates the space and calls `System.arraycopy()` under the hood. Can't get much more efficient than that. – Qwerky Feb 04 '11 at 10:21
  • 9
    How is copying a whole collection for every iteration **not** expensive? Moreover, you can get better than that, see Seans answer. – newgre Feb 04 '11 at 10:24
  • It also uses a native implementation to copy the memory, it doesn't iterate through the array. – Qwerky Feb 04 '11 at 10:35
  • 1
    Well if it is copying the array it is certainly an O(n) algorithm which does **not** scale and has the same complexity as iterating over the array once. Assume each list contains a million elements, then I need to copy a few million elements, only to iterate over them. Bad idea. – newgre Feb 04 '11 at 10:38
0

Here is my solution for that:

EDIT - changed code a little bit

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();
                }
            };
        }
    };
}
chmouel kalifa
  • 129
  • 2
  • 11
  • Your implementation flips the roles of `hasNext()` and `next()`. The first changes your iterator's state while the second doesn't. It should be the other way around. Calling `next()` without calling `hasNext()` will always yield `null`. Calling `hasNext()` without calling `next()` will throw away elements. Your `next()` doesn't throw `NoSuchElementException` either, but instead returns `null`. – xehpuk Dec 07 '15 at 01:51
  • You have re-invented `org.apache.commons.collections4.IterableUtils.chainedIterable()` – basin Aug 23 '20 at 15:34