Як я можу зібрати потік Java 8 у Guava ImmutableCollection?


82

Я хотів би зробити наступне:

List<Integer> list = IntStream.range(0, 7).collect(Collectors.toList());

але таким чином, що отриманий список є реалізацією Гуави ImmutableList.

Я знаю, що міг би це зробити

List<Integer> list = IntStream.range(0, 7).collect(Collectors.toList());
List<Integer> immutableList = ImmutableList.copyOf(list);

але я хотів би зібрати до нього безпосередньо. Я пробував

List<Integer> list = IntStream.range(0, 7)
    .collect(Collectors.toCollection(ImmutableList::of));

але він видав виняток:

java.lang.UnsupportedOperationException на com.google.common.collect.ImmutableCollection.add (ImmutableCollection.java:96)

Відповіді:


89

toImmutableList()Метод в прийнятому відповіді Алексіса тепер включений в гуави 21 і може бути використаний в якості:

ImmutableList<Integer> list = IntStream.range(0, 7)
    .boxed()
    .collect(ImmutableList.toImmutableList());

Edit: Вилучені @Betaз ImmutableList.toImmutableListпоряд з іншими часто використовуваними API , в версії 27.1 ( 6242bdd ).


1
Метод, позначений як @Beta. Отже, це не рекомендована документація документами?
user2602807

Все ще @Betaстаном на Guava 26.0.
Пер Лундберг,

З точки зору перспективи, Google утримував Gmail під бета-тегом між 2004 і 2009 роками, що вже було досить стабільним, зрілим продуктом на момент запуску в 2004 р. Google досить неохоче просуває продукти зі статусом бета-версії загалом. Майже до межі комедії.
anataliocs

67

Тут collectingAndThenкорисний колектор:

List<Integer> list = IntStream.range(0, 7).boxed()
                .collect(collectingAndThen(toList(), ImmutableList::copyOf));

Це застосовує трансформацію до Listщойно побудованого; в результаті чого ImmutableList.


Або ви можете безпосередньо зібрати в Builderі зателефонувати build()в кінці:

List<Integer> list = IntStream.range(0, 7)
                .collect(Builder<Integer>::new, Builder<Integer>::add, (builder1, builder2) -> builder1.addAll(builder2.build()))
                .build();

Якщо цей параметр для вас трохи багатослівний, і ви хочете використовувати його в багатьох місцях, ви можете створити власний колектор:

class ImmutableListCollector<T> implements Collector<T, Builder<T>, ImmutableList<T>> {
    @Override
    public Supplier<Builder<T>> supplier() {
        return Builder::new;
    }

    @Override
    public BiConsumer<Builder<T>, T> accumulator() {
        return (b, e) -> b.add(e);
    }

    @Override
    public BinaryOperator<Builder<T>> combiner() {
        return (b1, b2) -> b1.addAll(b2.build());
    }

    @Override
    public Function<Builder<T>, ImmutableList<T>> finisher() {
        return Builder::build;
    }

    @Override
    public Set<Characteristics> characteristics() {
        return ImmutableSet.of();
    }
}

і потім:

List<Integer> list = IntStream.range(0, 7)
                              .boxed()
                              .collect(new ImmutableListCollector<>());

Про всяк випадок, коли посилання зникає в коментарях; мій другий підхід можна було б визначити в статичному утилітному методі, який просто використовує Collector.of. Це простіше, ніж створити власний Collectorклас.

public static <T> Collector<T, Builder<T>, ImmutableList<T>> toImmutableList() {
    return Collector.of(Builder<T>::new, Builder<T>::add, (l, r) -> l.addAll(r.build()), Builder<T>::build);
}

та використання:

 List<Integer> list = IntStream.range(0, 7)
                               .boxed()
                               .collect(toImmutableList());

3
Це все ще створює проміжний список, чи не так? Я хотів би цього уникнути. Чи може це ImmutableList.Builderдопомогти?
Золтан

4
@ Zoltán Ви можете накопичувати значення в конструкторі безпосередньо (див. Редагування), а потім викликати build().
Alexis C.

4
Дякую за цю детальну відповідь. Здається, що зараз це вирішується: github.com/google/guava/issues/1582 , тут також є приємний приклад (багато в чому схожий на те, що ви запропонували): gist.github.com/JakeWharton/9734167
Золтан,

4
@ Золтан Ах так; хороші знахідки; він просто обертає другу альтернативу в корисні методи. Трохи краще, ніж визначати свій власний Collectorклас :-)
Алексіс К.

Типи посилань можуть бути ImmutableList<Integer>(замість List<Integer>).
palacsint

17

Хоча це не пряма відповідь на моє запитання (у ньому не використовуються колектори), це досить елегантний підхід, який не використовує проміжні колекції:

Stream<Integer> stream = IntStream.range(0, 7).boxed();
List<Integer> list = ImmutableList.copyOf(stream.iterator());

Джерело .


6

До речі: починаючи з JDK 10, це можна робити на чистій Java:

List<Integer> list = IntStream.range(0, 7)
    .collect(Collectors.toUnmodifiableList());

Також toUnmodifiableSetіtoUnmodifiableMap доступний.

Всередині колектора це було зроблено через List.of(list.toArray())


1
Це не зовсім так, оскільки ImmutableCollections.List12і ImmutableCollections.ListN! = Гуава ImmutableList. З практичної точки зору ви здебільшого маєте рацію, але все ж має сенс згадати цей нюанс у своїй відповіді.
Per Lundberg

4

FYI, існує розумний спосіб зробити це в Гуаві без Java 8:

ImmutableSortedSet<Integer> set = ContiguousSet.create(
    Range.closedOpen(0, 7), DiscreteDomain.integers());
ImmutableList<Integer> list = set.asList();

Якщо вам насправді не потрібна Listсемантика, а ви можете просто використовувати a NavigableSet, це ще краще, оскільки a ContiguousSetне повинен насправді зберігати всі елементи в ній (лише Rangeі DiscreteDomain).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.