2015-06-02 23 views
13

Załóżmy, że mam mapę z danymi nazwami, nazwami par i chcę znaleźć daną nazwę pierwszego wpisu na tej mapie, która ma nazwisko pasujące do określonej wartości. Jak to zrobimy w modzie Java.Java 8 wyodrębnia pierwszy klucz z pasującej wartości na mapie

W poniższym przykładzie przypadku testowego umieściłem dwa sposoby, aby to zrobić.

Jednak pierwszy (szukając imienia pierwszej osoby o nazwisku "Osioł") rzuci java.util.NoSuchElementException: Brak wartości, więc nie jest bezpieczny.

Drugi działa, ale nie tylko jest trudniejszy w czytaniu, ale jest nieco niezupełnie funkcjonalny.

Zastanawiam się, czy ktoś tutaj zaproponowałby mi łatwiejszy sposób na osiągnięcie tego przy pomocy stream() lub forEach() lub obu.

@Test 
public void shouldBeAbleToReturnTheKeyOfTheFirstMatchingValue() throws Exception { 
    Map<String, String> names = new LinkedHashMap<>(); 
    names.put("John", "Doe"); 
    names.put("Fred", "Flintstone"); 
    names.put("Jane", "Doe"); 
    String keyOfTheFirst = names.entrySet().stream().filter(e -> e.getValue().equals("Doe")).findFirst().get().getKey(); 
    assertEquals("John", keyOfTheFirst); 

    try { 
     names.entrySet().stream().filter(e -> e.getValue().equals("Donkey")).findFirst().get(); 
    } catch (NoSuchElementException e){ 
     // Expected 
    } 

    Optional<Map.Entry<String, String>> optionalEntry = names.entrySet().stream().filter(e -> e.getValue().equals("Donkey")).findFirst(); 
    keyOfTheFirst = optionalEntry.isPresent() ? optionalEntry.get().getKey() : null; 

    assertNull(keyOfTheFirst); 
} 

Dziękuję z góry.

+1

Oczywiście za pomocą dwukierunkowego mapę byłby bardziej efektywny. –

+0

Ten kod [zapachów] (http://en.wikipedia.org/wiki/Code_smell). Słaby projekt. –

+0

Cześć patryk. Całkowicie się z tobą zgadzam. Właśnie dlatego opublikowałem pytanie. Czy jesteś w stanie dostarczyć rozwiązanie? – Julian

Odpowiedz

41

Aby przywrócić wartość domyślną, jeśli nie ma meczu, użyj Optional#orElse

names.entrySet().stream() 
    .filter(e -> e.getValue().equals("Donkey")) 
    .map(Map.Entry::getKey) 
    .findFirst() 
    .orElse(null); 
+1

Thanks Misha i doon. Obie twoje odpowiedzi pomogły mi znaleźć rozwiązanie, które chciałem. Wybieram Mishę jako najbliższą. Aby powrócić do 'java.lang.String' ale nie' java.util.Optional' wszystko, co musiałem zrobić, to wywołać 'orElseGet (nowa Dostawca () { @Override public String get() { zerowy powrotu; } }) ' – Julian

+3

Jeśli naprawdę chcesz używać' orElseGet' zamiast 'orElse', można użyć wyrażenia lambda zamiast anonimowego klasy:' .orElseGet (() -> null) '. – Misha

+0

Dzięki Misha. Jest to jeszcze lepsze i było dokładnie tym, za czym byłem: czystym, funkcjonalnym rozwiązaniem tego problemu. ** Java 8 rocks! ** – Julian

0

Z podobnym question:

public static <T, E> Set<T> getKeysByValue(Map<T, E> map, E value) { 
    return map.entrySet() 
       .stream() 
       .filter(entry -> Objects.equals(entry.getValue(), value)) 
       .map(Map.Entry::getKey) 
       .collect(Collectors.toSet()); 
} 

Następnie można wybrać pierwsze, jeśli chcesz. Pamiętaj, że key jest wyjątkowy, value nie jest.

Edit: Cały kod (dzięki @Peter Lawrey)

package test; 

import java.util.LinkedHashMap; 
import java.util.Map; 
import java.util.Objects; 
import java.util.Optional; 

public class Main { 

    public static void main(String[] args) { 
     Map<String, String> names = new LinkedHashMap<>(); 
     names.put("John", "Doe"); 
     names.put("Fred", "Flintstone"); 
     names.put("Jane", "Doe"); 

     Optional<String> firstKey = names.entrySet().stream() 
       .filter(entry -> Objects.equals(entry.getValue(), "Doe")) 
       .map(Map.Entry::getKey).findFirst(); 

     if (firstKey.isPresent()) { 
      System.out.println(firstKey.get()); 
     } 
    } 
} 
+2

Zamiast 'collect' możesz użyć' findAny', aby znaleźć pierwszą znalezioną. –

+0

@PeterLawrey Dobrze przypomniane! Ale byłoby to 'findFirst', prawda? – Doon

+1

Byłoby to pierwsze, chyba że używasz parallelStream. –

0

Lubię staroświecki:

static <K, V> K findFirstKeyByValue(Map<K, V> map, String value) { 
    for (Entry<K, V> e : map.entrySet()) 
     if (e.getValue().equals(value)) 
      return e.getKey(); 
    return null; 
} 
1

Rozwiązanie dostarczone przez @Misha jest najlepsze, jeśli robisz nie chcesz używać kodu innej firmy. My library posiada specjalną metodę skrótu ofKeys dla takich przypadków jak odkryłem, że jest to dość powszechne zadanie:

StreamEx.ofKeys(names, "Donkey"::equals).findFirst().orElse(null); 
+0

Czy ten filtr nie ma klucza o nazwie osioł, a nie klucz odpowiadający wartości o nazwie osioł? – flup

+0

@flup, no, to filtr dla klucza odpowiadającego wartości o nazwie "Osioł". Aby filtrować klucz, możesz użyć zwykłej metody filtrowania, takiej jak 'StreamEx.ofKeys (names) .filter (" Donkey ":: equals)'. –

+0

aha! Niezła funkcja! – flup

Powiązane problemy