여러 컬렉션을 하나의 논리적 컬렉션으로 결합 하시겠습니까?


110

클래스의 멤버로 일정한 수의 컬렉션 (예 : 3 개의 ArrayLists)이 있다고 가정합니다. 이제 모든 요소를 ​​다른 클래스에 노출하여 모든 요소를 ​​간단히 반복 할 수 있도록합니다 (이상적으로는 읽기 전용). 나는 구아바 컬렉션을 사용하고 있는데 어떻게 구아바 이터 러블 / 반복자를 사용하여 임시 복사본 만들지 않고 내부 컬렉션에 대한 논리적 뷰를 생성 할 수 있는지 궁금합니다 .


^^ 깨진 링크. 나는 그가 가리키는이라고 생각 구아바의 Javadoc에서이 방법
RustyTheBoyRobot

답변:


113

Guava를 사용하면를 사용할 수 있습니다 Iterables.concat(Iterable<T> ...). 모든 이터 러블의 라이브 뷰를 생성하고 하나로 연결합니다 (이터 러블을 변경하면 연결된 버전도 변경됨). 그런 다음 연결된 iterable을 Iterables.unmodifiableIterable(Iterable<T>)(이전에 읽기 전용 요구 사항을 보지 못했던)로 래핑합니다 .

로부터 Iterables.concat( .. )JavaDoc을 :

여러 반복 가능 항목을 단일 반복 가능 항목으로 결합합니다. 반환 된 이터 러블에는 입력에서 각 이터 러블의 요소를 순회하는 이터레이터가 있습니다. 입력 반복자는 필요할 때까지 폴링되지 않습니다. 반환 된 이터 러블의 이터레이터는 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의 요청에 따라 라이브 컬렉션 뷰를 반환하는 유사한 메서드가 있습니다.

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
컬렉션은 어떻습니까? Iterables.concatIterable아니라 Collection. Collection보기 가 필요합니다 .
Nowaker 2011-08-02

@Damian의 유일한 유용한 기능은 집계 된 size () 메서드를 사용하는 것입니다. Collection 인터페이스의 다른 모든 메서드는 정의되지 않은 의미 (추가 등) 또는 비참한 성능 (포함 등)을 갖습니다.
Sean Patrick Floyd 2011 년

2
@Sean, 예- size()내가 필요한 것입니다. add()예외를 던지는 것이 좋습니다.이 방법은 신경 쓰지 않습니다. Collections API가 손상되어 아무도 그것에 대해 아무것도 할 수 없습니다. Collection.add(),, Iterator.remove()어쩌구.
Nowaker 2011-08-04

101

일반 자바 사용 a (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);
}

10

Java 8 이상을 사용하는 경우 다른 답변을 참조하십시오 .

이미 Google Guava를 사용하고 있다면 Sean Patrick Floyd의 답변을 참조하십시오 .

Java 7에 갇혀 있고 Google Guava를 포함하고 싶지 않다면 and 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();
        }
    };
}

1

당신은 그것에 대해 새로운 List것과 addAll()당신의 다른 것을 만들 수 있습니다 List. 그런 다음 수정 불가능한 목록을 Collections.unmodifiableList().


3
잠재적으로 비용이 많이 드는 새로운 임시 컬렉션을 생성합니다
newgre

6
비싼 방법은 , 목록의 기본 개체는 복사되지 않습니다 ArrayList단지 공간과 통화 할당 System.arraycopy()후드 아래를. 그것보다 훨씬 더 효율적일 수는 없습니다.
Qwerky

8
모든 반복에 대해 전체 컬렉션을 복사하는 것이 비싸지 않은 이유는 무엇입니까? 또한 그보다 더 나아질 수 있습니다. Seans 답변을 참조하십시오.
newgre

또한 기본 구현을 사용하여 메모리를 복사하며 배열을 반복하지 않습니다.
Qwerky

1
배열을 복사하는 경우 확실히 확장 되지 않고 배열을 한 번 반복하는 것과 동일한 복잡성을 갖는 O (n) 알고리즘입니다 . 각 목록에 백만 개의 요소가 포함되어 있다고 가정하면 몇 백만 개의 요소를 복사하여 반복해야합니다. 나쁜 생각.
newgre

0

이에 대한 내 해결책은 다음과 같습니다.

편집-약간의 코드 변경

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.
xehpuk
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.