Объединить несколько коллекций в одну логическую коллекцию?

110

Предположим, у меня есть постоянное количество коллекций (например, 3 ArrayLists) в качестве членов класса. Теперь я хочу предоставить все элементы другим классам, чтобы они могли просто перебирать все элементы (в идеале, только для чтения). Я использую коллекции guava, и мне интересно, как я могу использовать итераторы / итераторы guava для создания логического представления внутренних коллекций без создания временных копий.

Newgre
источник
^^ Неработающая ссылка. Я думаю, он указывал на этот метод в документации Guava Javadoc
RustyTheBoyRobot

Ответы:

113

С Guava вы можете использовать Iterables.concat(Iterable<T> ...), он создает живое представление всех итераций, объединенных в один (если вы измените итерации, объединенная версия также изменится). Затем оберните объединенную итерацию с помощью Iterables.unmodifiableIterable(Iterable<T>)(ранее я не видел требования только для чтения).

Из Iterables.concat( .. )JavaDocs:

Объединяет несколько итераций в одну итерацию. У возвращаемого итератора есть итератор, который просматривает элементы каждой итерации во входных данных. Итераторы ввода не опрашиваются до тех пор, пока это необходимо. Итератор возвращаемого итерационного объекта поддерживает, 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]


Редактировать:

По запросу Дамиана вот аналогичный метод, который возвращает представление коллекции в реальном времени.

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

}
Шон Патрик Флойд
источник
Как я могу запретить пользователю удалять элементы? Есть ли способ лучше, чем объединение списков в неизменяемые списки?
newgre
2
@jn_ просто заверните егоIterables.unmodifiableIterable(iterable)
Шон Патрик Флойд
2
А что насчет коллекций? Iterables.concatпроизводит Iterable, а не Collection. Мне нужен Collectionвид.
Новакер 02
@Damian - единственная полезная особенность этого - иметь агрегированный метод size (). Все другие методы в интерфейсе Collection будут либо иметь неопределенную семантику (добавить и т. Д.), Либо низкую производительность (содержит и т. Д.).
Шон Патрик Флойд,
2
@ Шон, да - size()это то, что мне нужно. add()выбрасывать исключение - это хорошо - меня не волнует этот метод. API коллекций не работает, и никто ничего не может с этим поделать. Collection.add(), Iterator.remove()Бла.
Новакер
101

Простые решения 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);
}
xehpuk
источник
10

Если вы используете хотя бы Java 8, см. Мой другой ответ .

Если вы уже используете Google Guava, см . Ответ Шона Патрика Флойда .

Если вы застряли на 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();
        }
    };
}
xehpuk
источник
1

Вы можете создать новый Listи addAll()свои другие List. Затем верните неизменяемый список с помощью Collections.unmodifiableList().

Qwerky
источник
3
Это создало бы новую временную коллекцию, которая потенциально может быть довольно дорогой
newgre
6
Дорого , как , основные объекты в списках не копируются и ArrayListпросто выделяет пространство и вызовы System.arraycopy()под капотом. Не может быть ничего более эффективного, чем это.
Qwerky
8
Как копировать всю коллекцию для каждой итерации не дорого? Более того, вы можете стать лучше, см. Ответ Шона.
newgre
Он также использует собственную реализацию для копирования памяти, он не выполняет итерацию по массиву.
Qwerky
1
Что ж, если он копирует массив, это определенно алгоритм O (n), который не масштабируется и имеет такую ​​же сложность, как итерация по массиву один раз. Предположим, что каждый список содержит миллион элементов, тогда мне нужно скопировать несколько миллионов элементов, только чтобы перебрать их. Плохая идея.
newgre
0

Вот мое решение для этого:

EDIT - немного изменил код

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
источник
Ваша реализация меняет роли hasNext()и next(). Первый изменяет состояние вашего итератора, а второй - нет. Должно быть наоборот. Звонок next()без звонка hasNext()всегда уступит null. Вызов hasNext()без вызова next()отбрасывает элементы. Ваш next()тоже не бросает NoSuchElementException, а вместо этого возвращается null.
xehpuk 07