2016-07-12 8 views
5

Próbuję zmienić jakiś niezbyt elegancki kod za pomocą strumienia. Mam HashMap zawierających ciągi i MyObjects a obecnie iteracji nad nim za pomocą pętli for tak:Jak mogę wykonać dwie różne funkcje w kolekcji na podstawie grupowania ze strumieniem?

Map<String, MyObject> map = new HashMap<>(); 
Map<String, MyObject> objectsToAdd = new HashMap<>(); 


for(MyObject object : map.values()){ 
     String idToAdd = object.getConnectedToId(); 

     if(StringUtils.isEmpty(idToAdd) { 
      continue; 
     } 

     if(idToAdd.substring(0,1).equals("i")){ // connected to an ICS 
      MyObject newObject = service1.someMethod(idToAdd); 

      if(newObject != null) { 
       objectsToAdd.put(newObject.getId(), newObject); 
      } 
     } else if (idToAdd.substring(0,1).equals("d")){ // connected to a device 
      MyObject newObject = service2.someMethod(idToAdd); 
      if(newObject != null) { 
       objectsToAdd.put(newObject.getId(), newObject); 
      } 
     } 

    } 

    map.putAll(objectsToAdd); 

Odkąd tylko dbają o identyfikatory, zacząłem za pomocą operacji mapie, aby uzyskać jedynie identyfikatory, a następnie operacja filtrowania w celu wyeliminowania pustych.

Następna część jest tym, z czym mam problem. Pierwszą rzeczą, jaką starał się za pomocą kolektorów operacji groupingBy tak, że mogę grupować elementy w oparciu o pierwszy znak identyfikatora i skończyło się tak:

 map.values().stream() 
      .map(myObject -> myObject.getConnectedToId()) // get a map of all the ids 
      .filter(StringUtils::isNotEmpty) // filter non empty ones 
      .collect(
       Collectors.mapping(
        MyObject::getId, 
        Collectors.toList())), 
         Collectors.groupingBy(
          s -> s.substring(0,1)); 

Ten link pomógł z redukcją z zastosowaniem kolektorów Stream: Stream Reduction

Mamy co najmniej dwa problemy z tym kodem: 1) collect to terminal operation, który zamknie strumień, a my jeszcze nie skończyliśmy, i 2) wciąż potrzebujemy oryginalnego obiektu, ale teraz został zredukowany do mapa connectToIds.

Q1) Czy istnieje pośrednia operacja, która pozwoli nam pogrupować obiekty na podstawie pierwszego znaku identyfikatora?

Q2) Jak możemy to zrobić, nie ograniczając kolekcji tylko do identyfikatorów?

Q3) I na koniec, gdy kolekcja zostanie zgrupowana (będą dwie), w jaki sposób możemy wykonać oddzielne funkcje w każdej grupie, tak jak w oryginalnym kodzie?


Ostateczne rozwiązanie (dzięki @Holger & @Flown za pomoc)

Map<Character, Function<String, MyObejct>> methodMapping = new HashMap<>(); 
    methodMapping.put('i', service1::method1); 
    methodMapping.put('d', service2::method2); 

    Map<String, MyObject> toAdd = map.values().stream().map(MyObject::getConnectedToId) 
     .filter(StringUtils::isNotEmpty) 
     .map(id -> methodMapping.getOrDefault(id.charAt(0), i -> null).apply(id)) 
     .filter(Objects::nonNull) 
     .collect(Collectors.toMap(MyObject::getId, Function.identity(), (mo1, mo2) -> mo2)); 

    map.putAll(toAdd); 

Aby uniknąć modyfikacji współbieżne wyjątku, konieczne jest najpierw zapisać obiekty w tymczasowym mapie podczas wykonywania operacje strumieniowe, a następnie po zakończeniu dodawania ich do ostatecznej mapy.

+1

Zakładam, że kluczem mapy jest 'id' z' MyObject'? To znaczy. 'map.get (object.getId()). equals (object) == true' – Flown

+0

Tak, klucz w HashMap jest identyfikatorem MyObject. Wprowadziłem małą poprawkę do pytania dla jasności - idToAdd to connectToId na myObject, a nie identyfikator obiektu. – Kristina

Odpowiedz

3

Twoje podejście i twoje wspólne podejście są bardzo różne pod względem typów zwrotów. Dlatego zmieniłem twoje poprzednie podejście do interfejsu API Stream.

Aby zmniejszyć część kodu, należy najpierw zbudować model Map<Character, Function<String, MyObject>>, aby wykonać zwięzłe wyszukiwanie w etapie mapowania.
wyglądać tak:

Map<Character, Function<String, MyObject>> serviceMapping = new HashMap<>(); 
serviceMapping.put('i', service1); 
serviceMapping.put('d', service2); 

Jak działa rurociągu?

  1. mapa MyObject ->MyObject::getConnectedToId
  2. filtr pusty Strings
  3. przeprowadzić wyszukiwanie w serviceMap.Jeśli jest obecny, a następnie powrócić Function<String, MyObject>, inaczej id -> null
  4. filtr null wartości
  5. Ostatnim krokiem jest zebranie wyników, dostarczając odpowiednie funkcje ekstrakcyjnych

Map<String, MyObject> toAdd = map.values().stream().map(MyObject::getConnectedToId) 
    .filter(StringUtils::isEmpty) 
    .map(id -> serviceMapping.getOrDefault(id.charAt(0), i -> null).apply(id)) 
    .filter(Objects::nonNull) 
    .collect(Collectors.toMap(MyObject::getId, Function.identity(), (mo1, mo2) -> mo2)); 
map.putAll(toAdd); 

It możliwe jest również dodanie obliczonych wartości bezpośrednio do map przy użyciu operacji forEach.

map.values().stream().map(MyObject::getConnectedToId) 
    .filter(StringUtils::isEmpty) 
    .map(id -> serviceMapping.getOrDefault(id.charAt(0), i -> null).apply(id)) 
    .filter(Objects::nonNull) 
    .forEach(mo -> map.put(mo.getId(), mo)); 
+0

Czy mo1 i mo2 są założone jako metoda1 i metoda2? Również w ostatnim zbiorczej operacji Dostaję „metoda dla statyczny nie można odwoływać się od statycznego kontekście” – Kristina

+2

@Kristina Jeśli istnieje kolizja już istniejących wartości domyślne wówczas 'kolekcjonerów :: toMap' będzie wyjątek . Dlatego musisz podać funkcję łączenia, która jest odpowiedzialna za podjęcie decyzji, który element jest zajęty, nową lub starą wartość ('(mo1, mo2) -> mo2'). – Flown

+0

@Holger Wszystko, aż działa 'map'. Musiałem wprowadzić jedną modyfikację do 'serviceMapping', ponieważ chce ona funkcji zwracającej' MyObject'. 'Mapa > methodMapping = new HashMap <>(); methodMapping.put ("i", service1 :: getMOById); methodMapping.put ('D', Usługa2 :: getMOById); ' Czy to wygląda prawda? Metody nie wydają się być wywoływane w tym przypadku, a zamiast tego operacja 'map' nie zwraca żadnych wyników – Kristina