Ich habe den folgenden Stream-Code:

List<Data> results = items.stream()
   .map(item -> requestDataForItem(item))
   .filter(data -> data.isValid())
   .collect(Collectors.toList());

Data requestDataForItem(Item item) {
   // call another service here
}

Das Problem ist, dass ich anrufen möchte requestDataForItem nur wenn alle Elemente im Stream gültig sind. Zum Beispiel, Wenn das erste Element ungültig ist, werde ich kein Element im Stream aufrufen.

Die Stream-API enthält .allMatch. aber es gibt ein boolean zurück. Ich möchte dasselbe tun wie .allMatch als .collect das Ergebnis, wenn alles übereinstimmt. Außerdem möchte ich Stream nur einmal verarbeiten, Mit zwei Schleifen ist es einfach.

Ist dies mit der Java Streams API möglich?

3
Orest 18 Apr. 2018 im 03:45

4 Antworten

Beste Antwort

Dies wäre ein Job für Java 9:

List<Data> results = items.stream()
    .map(item -> requestDataForItem(item))
    .takeWhile(data -> data.isValid())
    .collect(Collectors.toList());

Diese Operation wird beim ersten ungültigen Element gestoppt. Bei einer sequentiellen Ausführung bedeutet dies, dass keine nachfolgenden requestDataForItem Aufrufe ausgeführt werden. Bei einer parallelen Ausführung werden möglicherweise einige zusätzliche Elemente gleichzeitig verarbeitet, bevor der Vorgang beendet wird. Dies ist jedoch der Preis für eine effiziente parallele Verarbeitung.

In beiden Fällen enthält die Ergebnisliste nur die Elemente vor dem ersten ungültigen Element, und Sie können mit results.size() == items.size() leicht überprüfen, ob alle Elemente gültig waren.


In Java 8 gibt es keine so einfache Methode, und die Verwendung einer zusätzlichen Bibliothek oder die Einführung einer eigenen Implementierung von takeWhile würde sich nicht auszahlen, wenn man bedenkt, wie einfach die Nicht-Stream-Lösung wäre

List<Data> results = new ArrayList<>();
for(Item item: items) {
    Data data = requestDataForItem(item);
    if(!data.isValid()) break;
    results.add(data);
}
5
Holger 18 Apr. 2018 im 07:22

Implementieren Sie einen zweistufigen Prozess:

  1. Testen Sie, ob allMatch true zurückgibt.
  2. Wenn es true zurückgibt, führen Sie das Sammeln mit einem zweiten Stream durch.
1
DwB 18 Apr. 2018 im 01:12

Sie könnten theoretisch .allMatch verwenden und dann sammeln, wenn .allMatch true zurückgibt, aber , dann würden Sie die Sammlung zweimal verarbeiten. Es gibt keine Möglichkeit, das, was Sie versuchen, direkt mit der Streams-API zu tun.

Sie können eine Methode erstellen, um dies für Sie zu tun, und einfach Ihre Sammlung an diese übergeben, anstatt die Stream-API zu verwenden. Dies ist etwas weniger elegant als die Verwendung der Stream-API, aber effizienter, da die Sammlung nur einmal verarbeitet wird.

List<Data> results = getAllIfValid(
         items.stream().map(item -> 
             requestDataForItem(item).collect(Collectors.toList())
);

public List<Data> getAllIfValid(List<Data> items) {
    List<Data> results = new ArrayList<>();
    for (Data d : items) {
        if (!d.isValid()) {
            return new ArrayList<>();
        }
        results.add(d);
    }
    return results;
}

Dies gibt alle Ergebnisse zurück, wenn jedes Element übergeben wird und die items - Auflistung nur einmal verarbeitet. Wenn die Prüfung isValid() fehlschlägt, wird eine leere Liste zurückgegeben, da Sie alles oder nichts möchten. Überprüfen Sie einfach, ob die zurückgegebene Sammlung leer ist, um festzustellen, ob alle Elemente die Prüfung isValid() bestanden haben.

1
Jake Miller 18 Apr. 2018 im 01:24

Versuche dies.

List<Data> result = new ArrayList<>();
boolean allValid = items.stream()
    .map(item -> requestDataForItem(item))
    .allMatch(data -> data.isValid() && result.add(data));
if (!allValid)
    result.clear();
0
saka1029 18 Apr. 2018 im 04:28