Jak mogę zebrać strumień Java 8 w ImmutableCollection Guava?

82

Chciałbym wykonać następujące czynności:

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

ale w taki sposób, że wynikowa lista jest implementacją guawy ImmutableList.

Wiem, że mógłbym to zrobić

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

ale chciałbym odebrać do niego bezpośrednio. próbowałem

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

ale rzucił wyjątek:

java.lang.UnsupportedOperationException pod adresem com.google.common.collect.ImmutableCollection.add (ImmutableCollection.java:96)

Zoltán
źródło

Odpowiedzi:

89

toImmutableList()Metoda w przyjętym odpowiedź Alexis jest obecnie zawarte w Guava 21 i może być używany jako:

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

Edit: Usunięto @Betaz ImmutableList.toImmutableListwraz z innymi często używanych API w wersji 27.1 ( 6242bdd ).

Ritesh
źródło
1
Metoda oznaczona jako @Beta. Więc nie jest to zalecane przez doktorów?
user2602807
Jeszcze @Betaod Guava 26.0.
Per Lundberg
Ze względu na perspektywę Google utrzymywał Gmaila pod tagiem beta w latach 2004-2009, który był już dość stabilnym, dojrzałym produktem w momencie premiery w 2004 roku. Google raczej niechętnie promuje produkty ze statusu Beta w ogóle. Prawie do punktu komedii.
anataliocs
68

Tutaj collectingAndThenprzydaje się kolektor:

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

Stosuje transformację do Listwłaśnie zbudowanego; w wyniku czego ImmutableList.


Lub możesz bezpośrednio odebrać Builderi zadzwonić build()na koniec:

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

Jeśli ta opcja jest dla Ciebie nieco rozwlekła i chcesz jej używać w wielu miejscach, możesz stworzyć własny kolektor:

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

i wtedy:

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

Na wypadek, gdyby link zniknął z komentarzy; moje drugie podejście można zdefiniować w statycznej metodzie narzędziowej, która po prostu używa Collector.of. To prostsze niż tworzenie własnej Collectorklasy.

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

i zastosowanie:

 List<Integer> list = IntStream.range(0, 7)
                               .boxed()
                               .collect(toImmutableList());
Alexis C.
źródło
3
To nadal tworzy listę pośrednią, prawda? Chciałbym tego uniknąć. Czy to może ImmutableList.Builderbyć pomocne?
Zoltán
4
@ Zoltán Możesz gromadzić wartości bezpośrednio w kreatorze (patrz edycja), a następnie wywołać build().
Alexis C.
4
Dziękuję za szczegółową odpowiedź. Wygląda na to, że obecnie się tym zajmujemy : github.com/google/guava/issues/1582 , jest też ładny przykład (bardzo podobny do tego, co sugerowałeś): gist.github.com/JakeWharton/9734167
Zoltán
4
@ Zoltán Ah tak; dobre znaleziska; po prostu opakowuje drugą alternatywę w metody użytkowe. Trochę lepiej niż definiowanie własnej Collectorklasy :-)
Alexis C.
Typy referencyjne mogą być ImmutableList<Integer>(zamiast List<Integer>).
palacsint
17

Chociaż nie jest to bezpośrednia odpowiedź na moje pytanie (nie korzysta z kolekcjonerów), jest to dość eleganckie podejście, które nie wykorzystuje kolekcji pośrednich:

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

Źródło .

Zoltán
źródło
6

BTW: od JDK 10 można to zrobić w czystej Javie:

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

Również toUnmodifiableSetitoUnmodifiableMap dostępne.

Wewnątrz kolektora dokonano tego za pośrednictwem List.of(list.toArray())

Grigorij Kislin
źródło
1
Nie jest to do końca prawdą, ponieważ ImmutableCollections.List12i ImmutableCollections.ListN! = Guawa ImmutableList. Z praktycznego punktu widzenia masz w większości rację, ale mimo to wspomnienie o tym niuansie w Twojej odpowiedzi miałoby sens.
Per Lundberg
4

FYI, istnieje rozsądny sposób na zrobienie tego w Guava bez Java 8:

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

Jeśli w rzeczywistości nie potrzebujesz Listsemantyki i możesz po prostu użyć a NavigableSet, jest to jeszcze lepsze, ponieważ a ContiguousSetnie musi faktycznie przechowywać wszystkich elementów w nim (tylko Rangei DiscreteDomain).

ColinD
źródło