2016-06-24 8 views
17

Was ich tun möchte, wird unten in 2 Stream-Aufrufe angezeigt. Ich möchte eine Sammlung in zwei neue Kollektionen basierend auf einer Bedingung aufteilen. Idealerweise möchte ich es in 1 tun. Ich habe Bedingungen gesehen, die für die .map-Funktion von Streams verwendet wurden, konnte aber nichts für forEach finden. Was ist der beste Weg, um das zu erreichen, was ich will?Wie If-else Logik in Java 8 Stream forEach verwenden

animalMap.entrySet().stream() 
      .filter(pair-> pair.getValue() != null) 
      .forEach(pair-> myMap.put(pair.getKey(), pair.getValue())); 

    animalMap.entrySet().stream() 
      .filter(pair-> pair.getValue() == null) 
      .forEach(pair-> myList.add(pair.getKey())); 
+1

Scheint wie eine Situation, in der Ströme nicht wirklich tun Sie irgendwelche Gefälligkeiten. Es versteckt nur die Kontrollfluss-Syntax mit API in einer Weise, die sich als unangenehm herausstellt und Ihr forEach-Lambda statusbehaftet ist. – Radiodef

Antwort

28

Setzen Sie einfach die Bedingung in das Lambda selbst, z.

animalMap.entrySet().stream() 
     .forEach(
       pair -> { 
        if (pair.getValue() != null) { 
         myMap.put(pair.getKey(), pair.getValue()); 
        } else { 
         myList.add(pair.getKey()); 
        } 
       } 
     ); 

Natürlich setzt dies voraus, dass beide Sammlungen (myMap und myList) deklariert und vor dem oben Stück Code initialisiert.


Update:Map.forEach Verwendung macht den Code kürzer und effizienter und lesbar, wie Jorn Vernee freundlich vorgeschlagen:

animalMap.forEach(
      (key, value) -> { 
       if (value != null) { 
        myMap.put(key, value); 
       } else { 
        myList.add(key); 
       } 
      } 
    ); 
+3

Sie könnten '' 'Map.forEach''' stattdessen verwenden, es wäre ein wenig prägnanter. –

+1

@JornVernee, vielen Dank für den Hinweis. –

+1

Sie können im Lambda-Ausdruck auch Klammern '{...}' verwenden, wenn es mehr ist, als ein einfaches Ternär verarbeiten kann. – dcsohl

3

Das Problem von stream().forEach(..) mit einem Aufruf an add oder put innerhalb der Verwendung forEach (so dass Sie die externe myMap oder myList Instance mutieren) ist, dass Sie leicht in Nebenläufigkeit Probleme ausführen können, wenn jemand den Strom parallel schaltet a Die Sammlung, die Sie bearbeiten, ist nicht Thread-sicher.

Ein Ansatz, den Sie vornehmen können, besteht darin, zuerst die Einträge in der ursprünglichen Karte zu partitionieren. Sobald Sie das haben, greifen Sie die entsprechende Liste der Einträge und sammeln Sie sie in der entsprechenden Karte und Liste.

Map<Boolean, List<Map.Entry<K, V>>> partitions = 
    animalMap.entrySet() 
      .stream() 
      .collect(partitioningBy(e -> e.getValue() == null)); 

Map<K, V> myMap = 
    partitions.get(false) 
       .stream() 
       .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); 

List<K> myList = 
    partitions.get(true) 
       .stream() 
       .map(Map.Entry::getKey) 
       .collect(toList()); 

... oder, wenn Sie es in einem Durchlauf tun wollen, implementieren eine benutzerdefinierte Collector (unter der Annahme einer Tuple2<E1, E2> Klasse vorhanden ist, können Sie Ihre eigenen erstellen), zB:

public static <K,V> Collector<Map.Entry<K, V>, ?, Tuple2<Map<K, V>, List<K>>> customCollector() { 
    return Collector.of(
      () -> new Tuple2<>(new HashMap<>(), new ArrayList<>()), 
      (pair, entry) -> { 
       if(entry.getValue() == null) { 
        pair._2.add(entry.getKey()); 
       } else { 
        pair._1.put(entry.getKey(), entry.getValue()); 
       } 
      }, 
      (p1, p2) -> { 
       p1._1.putAll(p2._1); 
       p1._2.addAll(p2._2); 
       return p1; 
      }); 
} 

mit seinen Nutzung:

Tuple2<Map<K, V>, List<K>> pair = 
    animalMap.entrySet().parallelStream().collect(customCollector()); 

einzustellen, können Sie es, wenn Sie wollen, zum Beispiel durch ein Prädikat als Parameter angeben.

3

In den meisten Fällen sollten Sie bei der Verwendung von forEach in einem Stream überdenken, ob Sie das richtige Tool für Ihren Job verwenden oder ob Sie es richtig verwenden.

Im Allgemeinen sollten Sie nach einer geeigneten Terminaloperation suchen, die das erreicht, was Sie erreichen möchten, oder für einen geeigneten Collector. Jetzt gibt es Collectors für die Produktion von Map s und List s, aber kein Out-of-the-Box-Kollektor für die Kombination von zwei verschiedenen Kollektoren, basierend auf einem Prädikat.

Jetzt enthält this answer einen Kollektor zum Kombinieren zweier Kollektoren. Mit diesem Kollektor, können Sie die Aufgabe als

Pair<Map<KeyType, Animal>, List<KeyType>> pair = animalMap.entrySet().stream() 
    .collect(conditional(entry -> entry.getValue() != null, 
      Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue), 
      Collectors.mapping(Map.Entry::getKey, Collectors.toList()))); 
Map<KeyType,Animal> myMap = pair.a; 
List<KeyType> myList = pair.b; 

erreichen Aber vielleicht können Sie diese spezielle Aufgabe auf einfachere Art und Weise lösen. Eines Ihrer Ergebnisse entspricht dem Eingabetyp. Es ist die gleiche Karte, die nur die Einträge entfernt, die auf null abgebildet sind.Wenn Ihre ursprüngliche Karte wandelbar ist und Sie es danach nicht mehr benötigen, können Sie nur die Liste sammeln und diese Schlüssel aus der Original-Karte entfernen, da sie sich gegenseitig ausschließen:

List<KeyType> myList=animalMap.entrySet().stream() 
    .filter(pair -> pair.getValue() == null) 
    .map(Map.Entry::getKey) 
    .collect(Collectors.toList()); 

animalMap.keySet().removeAll(myList); 

Beachten Sie, dass Zuordnungen zu null entfernen auch ohne die Liste der anderen Tasten mit:

animalMap.values().removeIf(Objects::isNull); 

oder

animalMap.values().removeAll(Collections.singleton(null)); 

Wenn Sie die ursprüngliche Karte nicht ändern können (oder möchten), gibt es immer noch eine Lösung ohne einen benutzerdefinierten Kollektor. Wie in Alexis C.’s answer angedeutet, partitioningBy in die richtige Richtung geht, aber man kann es vereinfachen:

Map<Boolean,Map<KeyType,Animal>> tmp = animalMap.entrySet().stream() 
    .collect(Collectors.partitioningBy(pair -> pair.getValue() != null, 
       Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); 
Map<KeyType,Animal> myMap = tmp.get(true); 
List<KeyType> myList = new ArrayList<>(tmp.get(false).keySet()); 

Unterm Strich ist, nicht über gewöhnliche Sammlung Operationen vergessen, müssen Sie nicht alles, was mit dem tun, neue Stream-API

+0

Holger, stimmt das nicht zu? Ihre Lösung ist definitiv weniger lesbar als die akzeptierte? –

+2

@Marco Altieri: Das hängt von der eigentlichen Frage ab. Die Frage actu Ally fragte nach der * Stream API *, auf die die angenommene Antwort nicht wirklich antwortet, denn am Ende ist 'forEach 'nur eine alternative Syntax für eine' for'-Schleife. Wenn beispielsweise die Variante 'Map.forEach (...)' nicht parallel ausgeführt werden kann, wird die 'entrySet(). Stream(). ForEach (...)' Variante bei paralleler Ausführung schrecklich zerbrechen. Wenn Sie die Stream-API verwenden und verstehen möchten, wie Sie sie richtig verwenden können, müssen Sie mit der Antwort von Alexis C oder meiner gehen. Sobald du es verstanden hast, wird es dir nicht unlesbar erscheinen ... – Holger

0

Ich denke, es ist möglich, in Java 9: ​​

animalMap.entrySet().stream() 
       .forEach(
         pair -> Optional.ofNullable(pair.getValue()) 
           .ifPresentOrElse(v -> myMap.put(pair.getKey(), v), v -> myList.add(pair.getKey()))) 
       ); 

die ifPresentOrElse Notwendigkeit es allerdings zu arbeiten. (Ich denke, eine For-Schleife sieht besser aus.)