Wie kann ich einen Java 8-Stream in einer Guava ImmutableCollection sammeln?

83

Ich möchte Folgendes tun:

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

aber in einer Weise, dass die resultierende Liste eine Implementierung von Guava ist ImmutableList.

Ich weiß, ich könnte es tun

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

aber ich möchte es direkt sammeln. ich habe es versucht

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

aber es warf eine Ausnahme:

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

Zoltán
quelle

Antworten:

89

Die toImmutableList()Methode in der akzeptierten Antwort von Alexis ist jetzt in Guava 21 enthalten und kann verwendet werden als:

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

Edit: Entfernt @Betavon ImmutableList.toImmutableListzusammen mit anderen häufig verwendeten APIs in Freigabe 27,1 ( 6242bdd ).

Ritesh
quelle
1
Die als @Beta markierte Methode. Es wird also nicht von Docs als Apriory empfohlen?
user2602807
Noch @Betaab Guave 26.0.
Per Lundberg
Aus Gründen der Perspektive hat Google Google Mail zwischen 2004 und 2009 unter einem Beta-Tag gehalten, das bereits zum Start im Jahr 2004 ein ziemlich stabiles, ausgereiftes Produkt war. Google ist ziemlich zurückhaltend, Produkte mit Beta-Status im Allgemeinen zu bewerben. Fast bis zur Komödie.
Anataliocs
67

Hier ist der collectingAndThenSammler nützlich:

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

Es wendet die Transformation auf das an, was ListSie gerade gebaut haben. was zu einem ImmutableList.


Oder Sie können direkt in die sammeln Builderund build()am Ende anrufen :

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

Wenn diese Option für Sie etwas ausführlich ist und Sie sie an vielen Stellen verwenden möchten, können Sie Ihren eigenen Sammler erstellen:

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

und dann:

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

Nur für den Fall, dass der Link in den Kommentaren verschwindet; Mein zweiter Ansatz könnte in einer statischen Dienstprogrammmethode definiert werden, die einfach verwendet Collector.of. Es ist einfacher als eine eigene CollectorKlasse zu erstellen .

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

und die Verwendung:

 List<Integer> list = IntStream.range(0, 7)
                               .boxed()
                               .collect(toImmutableList());
Alexis C.
quelle
3
Dadurch wird immer noch eine Zwischenliste erstellt, nicht wahr? Das möchte ich vermeiden. Könnte das ImmutableList.Builderhilfreich sein?
Zoltán
4
@ Zoltán Sie können die Werte direkt im Builder akkumulieren (siehe Bearbeiten) und dann aufrufen build().
Alexis C.
4
Vielen Dank für diese ausführliche Antwort. Es scheint, dass dies derzeit behoben wird : github.com/google/guava/issues/1582 , hier gibt es auch ein schönes Beispiel (ähnlich wie Sie es vorgeschlagen haben): gist.github.com/JakeWharton/9734167
Zoltán
4
@ Zoltán Ah ja; gute Funde; Die zweite Alternative wird einfach in Dienstprogrammmethoden eingeschlossen. Ein bisschen besser als deine eigene CollectorKlasse zu definieren :-)
Alexis C.
Referenztypen könnten ImmutableList<Integer>(anstelle von List<Integer>) sein.
Palacsint
17

Dies ist zwar keine direkte Antwort auf meine Frage (es werden keine Sammler verwendet), aber dies ist ein ziemlich eleganter Ansatz, bei dem keine Zwischensammlungen verwendet werden:

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

Quelle .

Zoltán
quelle
6

Übrigens: seit JDK 10 kann es in reinem Java gemacht werden:

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

Auch toUnmodifiableSetund toUnmodifiableMapverfügbar.

Innerhalb des Sammlers wurde es über gemacht List.of(list.toArray())

Grigory Kislin
quelle
1
Dies ist nicht genau richtig, da ImmutableCollections.List12und ImmutableCollections.ListN! = Guavas ImmutableList. Aus praktischer Sicht haben Sie größtenteils Recht, aber es wäre dennoch sinnvoll, diese Nuance in Ihrer Antwort zu erwähnen.
Per Lundberg
4

Zu Ihrer Information, es gibt einen vernünftigen Weg, dies in Guava ohne Java 8 zu tun:

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

Wenn Sie die ListSemantik nicht wirklich brauchen und einfach eine verwenden könnenNavigableSet können, ist dies sogar noch besser, da a ContiguousSetnicht alle Elemente darin speichern muss (nur das Rangeund DiscreteDomain).

ColinD
quelle