2015-06-02 3 views
13

Angenommen, ich habe eine Karte mit Vornamen, Nachnamen und möchte den Vornamen des ersten Eintrags in dieser Karte finden, der den Nachnamen mit einem bestimmten Wert hat. Wie würden wir das in einer Java 8 Mode machen.Java 8 extrahiere ersten Schlüssel von passendem Wert in einer Karte

In meinem Testfall Beispiel unten habe ich zwei Möglichkeiten, die es tun würde.

Die erste (Suche nach dem Vornamen der ersten Person mit dem Nachnamen "Donkey") wird java.util.NoSuchElementException werfen: Kein Wert vorhanden, so dass es nicht sicher ist.

Die zweite funktioniert, aber es ist nicht nur schwieriger zu lesen, aber es ist ein bisschen nicht ganz funktionsfähig.

Ich frage mich nur, ob jemand hier mir einen einfacheren, klareren Weg vorschlagen würde, dies entweder mit stream() oder forEach() oder beidem zu erreichen.

@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); 
} 

Vielen Dank im Voraus.

+1

Offensichtlich eine bidirektionale Karte unter wäre effizienter. –

+0

Dieser Code [riecht] (http://en.wikipedia.org/wiki/Code_smell). Schlechtes Design. –

+0

Hallo Patryk. Ich stimme dir vollkommen zu. Deshalb habe ich die Frage gestellt. Können Sie eine Lösung anbieten? – Julian

Antwort

41

einen Standardwert zurück, wenn es keine Übereinstimmung gibt, verwenden Optional#orElse

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

Dank Misha und Doon. Beide Antworten haben mir geholfen, die Lösung zu finden, die ich wollte. Ich wähle Misha als das, was mir am nächsten ist. Um eine 'java.lang.String' zurückzukehren, aber kein' java.util.Optional' alles, was ich tun musste, war zu berufen 'orElseGet (neu Lieferant () { @Override public String get() { return null; } }) ' – Julian

+3

Wenn Sie wirklich wollen' orElseGet' statt 'orElse' verwenden, können Sie einen Lambda-Ausdruck anstelle der anonymen Klasse verwenden können:' .orElseGet (() -> null) '. – Misha

+0

Danke Misha. Das ist noch besser und genau das, was ich suchte: eine rein funktionale Lösung für dieses Problem. ** Java 8 rockt! ** – Julian

0

Von einem ähnlichen 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()); 
} 

Dann können Sie die erste wählen, wenn Sie möchten. Denken Sie daran, dass die key ist einzigartig, die value ist nicht.

Edit: Der gesamte Code (Danke @ 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

Anstelle von 'collect' können Sie' findAny' verwenden, um den ersten gefunden zu bekommen. –

+0

@PeterLawrey Gut erinnert! Aber es wäre "findFirst", oder? – Doon

+1

Es wäre zuerst, es sei denn, Sie verwenden parallelStream. –

0

Ich mag altmodisch:

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

Die Lösung von @Misha ist die beste, wenn Sie dies tun nicht den Code von Drittanbietern verwenden möchten. My library hat die spezielle Shortcut-Methode ofKeys für solche Fälle als ich entdeckte, dass es ziemlich gemeinsame Aufgabe:

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

Wird nicht nach einem Schlüssel namens Esel gefiltert, und nicht nach dem Schlüssel, der einem Wert namens Esel entspricht? – flup

+0

@flup, nein, es ist ein Filter für den Schlüssel, der einem Wert namens "Donkey" entspricht. Um einen Schlüssel zu filtern, können Sie die übliche Filtermethode wie 'StreamEx.ofKeys (names) .filter (" Donkey ":: equals)' verwenden. –

+0

aha! Nettes Feature! – flup

Verwandte Themen