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


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.concat이 Iterable아니라 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.