115 votes

Combiner plusieurs collections en une seule collection logique ?

Supposons que j'ai un nombre constant de collections (par exemple 3 ArrayLists) comme membres d'une classe. Maintenant, je veux exposer tous les éléments à d'autres classes pour qu'elles puissent simplement itérer sur tous les éléments (idéalement, en lecture seule). J'utilise les collections de Guava et je me demande comment je pourrais utiliser les itérables/iterators de Guava pour générer une vue logique sur les collections internes. sans faire des copies temporaires.

0 votes

^^ Lien brisé. Je pense qu'il pointait vers cette méthode dans la Javadoc de Guava

118voto

Sean Patrick Floyd Points 109428

Avec la goyave, vous pouvez utiliser Iterables.concat(Iterable<T> ...) Il crée une vue en direct de toutes les itérables, concaténées en une seule (si vous modifiez les itérables, la version concaténée change également). Enveloppez ensuite l'itérable concaténée avec Iterables.unmodifiableIterable(Iterable<T>) (Je n'avais pas vu l'exigence de lecture seule plus tôt).

De la Iterables.concat( .. ) JavaDocs :

Combine plusieurs itérables en un seul unique itérable. L'itérable retourné retournée possède un itérateur qui parcourt les éléments de chaque itérable en entrée. Les itérateurs d'entrée ne sont pas interrogés jusqu'à ce que cela soit nécessaire. L'itérable retourné retourné supporte remove() lorsque l'itérateur d'entrée correspondant le supporte.

Bien qu'il ne soit pas explicitement dit qu'il s'agit d'une vue en direct, la dernière phrase laisse entendre que c'est le cas (en appuyant le Iterator.remove() seulement si l'itérateur de support le supporte, ce qui n'est pas possible à moins d'utiliser une vue en direct).

Code échantillon :

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

Sortie :

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


Edit :

À la demande de Damian, voici une méthode similaire qui renvoie une vue de collection en direct

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

}

0 votes

Comment empêcher l'utilisateur de supprimer des éléments ? Existe-t-il un moyen plus agréable que d'envelopper les listes dans des UnmodifiableLists ?

2 votes

@jn_ juste l'emballer dans Iterables.unmodifiableIterable(iterable)

2 votes

Qu'en est-il des collections ? Iterables.concat produit un Iterable pas Collection . J'aurais besoin d'un Collection vue.

103voto

xehpuk Points 645

Solutions simples de Java 8 à l'aide d'un Stream .

Nombre constant

En supposant que private Collection<T> c, c2, c3 .

Une solution :

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

Une autre solution :

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

Numéro de la variable

En supposant que private Collection<Collection<T>> cs :

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

10voto

xehpuk Points 645

Si vous utilisez au moins Java 8, voir mon autre réponse .

Si vous utilisez déjà Google Guava, voir Réponse de Sean Patrick Floyd .

Si vous êtes bloqué à Java 7 et que vous ne voulez pas inclure Google Guava, vous pouvez écrire votre propre (lecture seule) Iterables.concat() en n'utilisant pas plus de Iterable et Iterator :

Nombre constant

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

Numéro de la variable

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

2voto

Qwerky Points 10847

Vous pourriez créer un nouveau List et addAll() de vos autres List à ce sujet. Puis on retourne une liste non modifiable avec Collections.unmodifiableList() .

3 votes

Cela créerait une nouvelle collection temporaire qui pourrait être assez coûteuse.

7 votes

Coûteux comment les objets sous-jacents dans les listes ne sont pas copiés et ArrayList alloue simplement l'espace et appelle System.arraycopy() sous le capot. On ne peut pas faire plus efficace que ça.

8 votes

Comment copier une collection entière pour chaque itération ? pas coûteux ? De plus, vous pouvez obtenir mieux que cela, voir la réponse de Sean.

0voto

chmouel kalifa Points 119

Voici ma solution pour cela :

EDIT - code modifié un peu

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X