¿Cómo puedo recopilar una secuencia de Java 8 en una colección inmutable de Guava?

82

Me gustaría hacer lo siguiente:

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

pero de una manera que la lista resultante es una implementación de la de Guava ImmutableList.

Sé que podría hacer

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

pero me gustaría cobrarle directamente. He intentado

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

pero arrojó una excepción:

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

Zoltán
fuente

Respuestas:

89

El toImmutableList()método en la respuesta aceptada de Alexis ahora se incluye en Guava 21 y se puede usar como:

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

Editar: Fuera @Betade ImmutableList.toImmutableListjunto con otras APIs utilizan con frecuencia en Release 27.1 ( 6242bdd ).

Ritesh
fuente
1
El método marcado como @Beta. ¿Entonces no es recomendado previamente por docs?
user2602807
Aún a @Betapartir de Guayaba 26.0.
Per Lundberg
En aras de la perspectiva, Google mantuvo Gmail bajo una etiqueta beta entre 2004 y 2009, que ya era un producto maduro y bastante estable en su lanzamiento en 2004. Google es bastante reacio a promocionar productos del estado Beta en general. Casi hasta el punto de la comedia.
anataliocs
68

Aquí es donde el collectingAndThencolector es útil:

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

Aplica la transformación a la Listque acaba de construir; resultando en un ImmutableList.


O puede cobrar directamente en el Buildery llamar build()al final:

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

Si esta opción es un poco detallada para usted y desea usarla en muchos lugares, puede crear su propio recopilador:

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

y entonces:

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

En caso de que el enlace desaparezca en los comentarios; mi segundo enfoque podría definirse en un método de utilidad estática que simplemente usaCollector.of . Es más sencillo que crear tu propia Collectorclase.

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

y el uso:

 List<Integer> list = IntStream.range(0, 7)
                               .boxed()
                               .collect(toImmutableList());
Alexis C.
fuente
3
Esto todavía crea una lista intermedia, ¿no? Me gustaría evitar eso. ¿Podría ImmutableList.Builderser de alguna ayuda?
Zoltán
4
@ Zoltán Puede acumular los valores en el constructor directamente (ver editar) y luego llamar build().
Alexis C.
4
Gracias por esta respuesta detallada. Parece que esto se está abordando actualmente: github.com/google/guava/issues/1582 , también hay un buen ejemplo aquí (muy parecido a lo que sugirió): gist.github.com/JakeWharton/9734167
Zoltán
4
@ Zoltán Ah sí; buenos hallazgos; simplemente envuelve la segunda alternativa en métodos de utilidad. Un poco mejor que definir tu propia Collectorclase :-)
Alexis C.
Los tipos de referencia podrían ser ImmutableList<Integer>(en lugar de List<Integer>).
palacsint
17

Si bien no es una respuesta directa a mi pregunta (no usa colectores), este es un enfoque bastante elegante que no usa colecciones intermedias:

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

Fuente .

Zoltán
fuente
6

Por cierto: desde JDK 10 se puede hacer en Java puro:

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

También toUnmodifiableSetytoUnmodifiableMap disponible.

Dentro del colector se hizo a través de List.of(list.toArray())

Grigory Kislin
fuente
1
Esto no es exactamente cierto, ya que ImmutableCollections.List12y ImmutableCollections.ListN! = Guayaba ImmutableList. Desde una perspectiva práctica, en su mayoría tiene razón, pero aún tendría sentido mencionar este matiz en su respuesta.
Per Lundberg
4

Para su información, hay una forma razonable de hacer esto en Guava sin Java 8:

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

Si en realidad no necesita la Listsemántica y solo puede usar a NavigableSet, eso es aún mejor, ya ContiguousSetque a no tiene que almacenar todos los elementos en él (solo el Rangey DiscreteDomain).

ColinD
fuente