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)

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 ).


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

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 .


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


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).

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.