[JAVA] Punkt 47: Sammlung als Rückgabetyp dem Stream vorziehen

47. Die Sammlung sollte aus dem Stream als Rückgabetyp ausgewählt werden

In welchem Typ sollte ein zusammenhängendes Element zurückgegeben werden?

Mögliche Rückgabetypen für zusammenhängende Elemente sind Sammlungsschnittstellen, Iterables, Arrays und Streams.

Zurück im Stream

Sie können hören, dass es gut ist, in einem Stream zurückzukehren, aber wie in Punkt 45 erwähnt, ist es wichtig, den Stream von der Iteration zu trennen.

Da Stream nicht von Iterable erbt, besteht die einzige Möglichkeit, die for-each-Anweisung zum Umdrehen des als Stream zurückgegebenen Werts zu verwenden, in der Verwendung der Iteratormethode von Stream. Es scheint, dass der folgende Code mit der Iterator-Methode gut funktioniert.

// Won't compile, due to limitations on Java's type inference
for (ProcessHandle ph : ProcessHandle.allProcesses()::iterator) {
    // Process the process
}

Dieser Code kann jedoch nicht kompiliert werden und muss wie folgt umgewandelt werden.

// Hideous workaround to iterate over a stream
for  (ProcessHandle ph : (Iterable<ProcessHandle>)
                        ProcessHandle.allProcesses()::iterator)

Dieser Code funktioniert, ist aber chaotisch und verwirrend. Eine Alternative ist die Verwendung der Adaptermethode. Das JDK bietet keine solche Methode, aber Sie können sie einfach so schreiben:

// Adapter from  Stream<E> to Iterable<E>
public static <E> Iterable<E> iterableOf(Stream<E> stream) {
    return stream::iterator;
}

Mit dieser Adaptermethode ist es möglich, für jeden Stream wie folgt zu drehen.

for (ProcessHandle p : iterableOf(ProcessHandle.allProcesses())) {
    // Process the process
}

Kehre mit Iterable zurück

Im Gegenteil, selbst wenn der Client versucht, es als Stream zu verarbeiten, der Rückgabewert jedoch nur Iterable entspricht, muss er behandelt werden. Diese Korrespondenz wird in JDK nicht vorbereitet, aber Sie können die entsprechende Methode einfach wie folgt schreiben.

// Adapter from Iterable<E> to Stream<E>
public static <E> Stream<E> streamOf(Iterable<E> iterable) {
    return StreamSupport.stream(iterable.spliterator(), false);
}

In Sammlung zurückgeben

Die Collection-Schnittstelle ist ein Subtyp von Iterable und verfügt auch über Stream-Methoden, sodass sowohl die Iterationsverarbeitung als auch die Stream-Verarbeitung ausgeführt werden können. Daher ist der optimale Rückgabetyp für eine Methode, die ** zusammenhängende Elemente zurückgibt, normalerweise eine Sammlung oder ein geeigneter Untertyp der Sammlung **. Wenn die zurückzugebenden zusammenhängenden Elemente klein genug sind, können Sie eine Implementierung einer Sammlung wie ArrayList oder HashSet zurückgeben. Sie sollten jedoch keine großen zusammenhängenden Elemente im Speicher speichern, um sie als ** Sammlung zurückzugeben. ** ** **

Wenn die zurückzugebenden fortlaufenden Elemente groß sind, aber präzise ausgedrückt werden können, sollten Sie eine spezielle Sammlung implementieren. Stellen Sie sich beispielsweise eine Implementierung vor, die einen Potenzsatz eines bestimmten Satzes zurückgibt. Was ist eine Potenzmenge? Eine Potenzmenge von {a, b, c} ist beispielsweise {{}, {a}, {b}, {c}, {a, b}, {a, c}, {b, Es sieht aus wie c}, {a, b, c}}, und wenn es eine Menge von n Elementen gibt, gibt es eine Potenzmenge von 2 zur n-ten Potenz. Denken Sie nicht daran, das Power-Set in eine Standard-Sammlung aufzunehmen, da es sich um ein sehr großes Set handelt. Eine benutzerdefinierte Sammlung, die dies erreicht, kann mithilfe der AbstractList einfach implementiert werden. Der Mechanismus besteht darin, jedes Element der Menge zu indizieren und zu bestimmen, ob sie bitweise existieren oder nicht. Der Code sieht folgendermaßen aus:

package tryAny.effectiveJava;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

//Returns the power set of an input set as custom collection
public class PowerSet {
    public static final <E> Collection<Set<E>> of(Set<E> s) {
        List<E> src = new ArrayList<>(s);
        if (src.size() > 30)
            throw new IllegalArgumentException("Set too big " + s);
        return new AbstractList<Set<E>>() {
            @Override
            public int size() {
                return 1 << src.size(); // 2 to the power srcSize
            }

            @Override
            public boolean contains(Object o) {
                return o instanceof Set && src.containsAll((Set) o);
            }

            @Override
            public Set<E> get(int index) {
                Set<E> result = new HashSet<>();
                for (int i = 0; index != 0; i++, index >>= 1)
                    if ((index & 1) == 1)
                        result.add(src.get(i));
                return result;
            }
        };
    }
}

Im obigen Code wird eine Ausnahme ausgelöst, wenn die Menge 30 oder mehr Elemente enthält. Dies liegt daran, dass der maximale Wert, der von der Größenmethode der Sammlung zurückgegeben werden kann, 2 bis 31. Potenz-1 beträgt.

In einigen Fällen wird der zurückzugebende Typ nur durch die Schwierigkeit der Implementierung bestimmt. Sie können beispielsweise eine Methode schreiben, die alle Unterlisten der Eingabeliste zurückgibt. Sie können drei Codezeilen schreiben, um Unterlisten zu erstellen und diese in eine Standardsammlung einzufügen. Der Speicher muss jedoch eine zweidimensionale Sammlung enthalten. Dies ist nicht schlecht im Vergleich zu einem Satz, der exponentiell wachsen sollte, aber es ist nicht akzeptabel. Das Implementieren einer benutzerdefinierten Sammlung wie bei Stromversorgungssätzen ist mühsam.

Das Zurückgeben aller Unterlisten einer Liste als Stream kann jedoch mit ein wenig Einfallsreichtum direkt implementiert werden. Die Unterlisten, die das erste Zeichen der Liste enthalten, werden als Präfixe bezeichnet. Das heißt, die Präfixe von (a, b, c) sind (a), (a, b), (a, b, c). Die Unterlisten, die das letzte Zeichen der Liste enthalten, werden als Suffixe bezeichnet. Das heißt, die Suffixe von (a, b, c) sind (a, b, c), (b, c), (c). Zu diesem Zeitpunkt sind alle Unterlisten der Liste die Suffixe der Listenpräfixe und eine leere Liste. Die Implementierung ist wie folgt.

package tryAny.effectiveJava;

import java.util.Collections;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;

//Returns a stream of all the sublists of its input list
public class SubLists {
    public static <E> Stream<List<E>> of(List<E> list) {
        return Stream.concat(Stream.of(Collections.emptyList()), prefixes(list).flatMap(SubLists::suffixes));
    }

    private static <E> Stream<List<E>> prefixes(List<E> list) {
        return IntStream.rangeClosed(1, list.size()).mapToObj(end -> list.subList(0, end));
    }

    private static <E> Stream<List<E>> suffixes(List<E> list) {
        return IntStream.range(0, list.size()).mapToObj(start -> list.subList(start, list.size()));
    }
}

Dieser Code hat die gleiche Idee wie der verschachtelte der normalen for-Schleife wie unten.

for (int start = 0; start < src.size(); start++)
    for (int end = start + 1; end <= src.size(); end++)
        System.out.println(src.subList(start, end));

Eine wörtliche Übersetzung dieser for-Schleife in die Stream-Verarbeitung wäre einfacher, aber weniger lesbar. Insbesondere ist es wie folgt.

// Returns a stream of all the sublists of its input list
public static <E> Stream<List<E>> of(List<E> list) {
   return IntStream.range(0, list.size())
      .mapToObj(start ->
         IntStream.rangeClosed(start + 1, list.size())
            .mapToObj(end -> list.subList(start, end)))
      .flatMap(x -> x);
}

Keine der Implementierungen ist schlecht, aber einige Benutzer benötigen möglicherweise Code zum Konvertieren aus einem Stream, damit dieser iteriert werden kann, oder sie müssen das Streaming ausführen, wenn die iterative Verarbeitung natürlich ist. Der Code, der aus dem Stream konvertiert wird, damit er iteriert werden kann, überfrachtet nicht nur den Clientcode, sondern weist auch Leistungsprobleme im Vergleich zur Implementierung in Collection auf.

Recommended Posts

Punkt 47: Sammlung als Rückgabetyp dem Stream vorziehen
Punkt 28: Listen Arrays vorziehen
Punkt 65: Schnittstellen der Reflexion vorziehen
Punkt 43: Bevorzugen Sie Methodenverweise auf Lambdas
Punkt 42: Bevorzugen Sie Lambdas gegenüber anonymen Klassen
Punkt 39: Bevorzugen Sie Anmerkungen gegenüber Namensmustern
Punkt 85: Bevorzugen Sie Alternativen zur Java-Serialisierung
Punkt 58: Bevorzugen Sie für jede Schleife herkömmliche Schleifen
Punkt 23: Bevorzugen Sie Klassenhierarchien gegenüber markierten Klassen
Punkt 61: Bevorzugen Sie primitive Typen gegenüber primitiven Boxen
Ich suche nach einer Möglichkeit, Ja / Nein von Dialog als booleschen Wert zurückzugeben ...
Punkt 81: Bevorzugen Sie Parallelitätsdienstprogramme, um zu warten und zu benachrichtigen
So stellen Sie Struts2 manuell als Kriegsdatei bereit
Punkt 80: Ziehen Sie Executoren, Aufgaben und Streams Threads vor
Übergeben Sie ein Argument an die Methode und erhalten Sie das Ergebnis der Operation als Rückgabewert