Como posso coletar um fluxo Java 8 em um Guava ImmutableCollection?

82

Eu gostaria de fazer o seguinte:

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

mas de uma forma que a lista resultante é uma implementação da Goiaba ImmutableList.

Eu sei que poderia fazer

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

mas eu gostaria de cobrar diretamente. eu tentei

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

mas lançou uma exceção:

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

Zoltán
fonte

Respostas:

89

O toImmutableList()método na resposta aceita de Alexis agora está incluído no Guava 21 e pode ser usado como:

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

Editar: Removido @Betade ImmutableList.toImmutableList, juntamente com outros APIs usadas frequentemente em lançamento 27.1 ( 6242bdd ).

Ritesh
fonte
1
O método marcado como @Beta. Então não é recomendado pelos médicos?
user2602807
Ainda @Betaem goiaba 26.0.
Por Lundberg
Por uma questão de perspectiva, o Google manteve o Gmail sob a marca beta entre 2004 e 2009, que já era um produto bastante estável e maduro no lançamento em 2004. O Google está bastante relutante em promover produtos do status Beta em geral. Quase ao ponto da comédia.
anataliocs
68

É aqui que o collectingAndThencoletor é útil:

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

Ele aplica a transformação ao que Listvocê acabou de construir; resultando em um ImmutableList.


Ou você pode cobrar diretamente no Buildere ligar build()no final:

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

Se esta opção for um pouco prolixa para você e quiser usá-la em muitos lugares, você pode criar seu próprio coletor:

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

e depois:

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

Para o caso de o link desaparecer nos comentários; minha segunda abordagem poderia ser definida em um método utilitário estático que simplesmente usa Collector.of. É mais simples do que criar sua própria Collectorclasse.

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

e o uso:

 List<Integer> list = IntStream.range(0, 7)
                               .boxed()
                               .collect(toImmutableList());
Alexis C.
fonte
3
Isso ainda cria uma lista intermediária, não é? Eu gostaria de evitar isso. Isso poderia ImmutableList.Builderser de alguma ajuda?
Zoltán
4
@ Zoltán Você pode acumular os valores no construtor diretamente (veja a edição) e então chamar build().
Alexis C.
4
Obrigado por esta resposta detalhada. Parece que isso está sendo resolvido: github.com/google/guava/issues/1582 , também há um bom exemplo aqui (muito parecido com o que você sugeriu): gist.github.com/JakeWharton/9734167
Zoltán
4
@ Zoltán Ah sim; boas descobertas; ele simplesmente envolve a segunda alternativa em métodos utilitários. Um pouco melhor do que definir sua própria Collectorclasse :-)
Alexis C.
Os tipos de referência podem ser ImmutableList<Integer>(em vez de List<Integer>).
palacsint
17

Embora não seja uma resposta direta à minha pergunta (não usa coletores), esta é uma abordagem bastante elegante que não usa coleções intermediárias:

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

Fonte .

Zoltán
fonte
6

BTW: desde o JDK 10, isso pode ser feito em Java puro:

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

Também toUnmodifiableSete toUnmodifiableMapdisponíveis.

No coletor interno foi feito via List.of(list.toArray())

Grigory Kislin
fonte
1
Isso não é exatamente verdade, pois ImmutableCollections.List12e ImmutableCollections.ListN! = Goiaba ImmutableList. De uma perspectiva prática, você está correto, mas ainda faria sentido mencionar essa nuance em sua resposta.
Por Lundberg
4

Para sua informação, há uma maneira razoável de fazer isso no Guava sem Java 8:

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

Se você realmente não precisa da Listsemântica e pode apenas usar um NavigableSet, isso é ainda melhor, já ContiguousSetque a não precisa realmente armazenar todos os elementos nele (apenas o Rangee DiscreteDomain).

ColinD
fonte