【问题标题】:Java 8 extract first key from matching value in a MapJava 8 从 Map 中的匹配值中提取第一个键
【发布时间】:2015-06-02 23:37:10
【问题描述】:

假设我有一个名字、姓氏对的映射,并且我想找到该映射中第一个具有与某个值匹配的姓氏的条目的名字。 我们如何以 java 8 的方式做到这一点。

在下面的测试用例示例中,我提出了两种方法。

但是第一个(寻找姓氏为“驴”的第一个人的名字)将抛出 java.util.NoSuchElementException: No value present so it is not safe。

第二个有效,但它不仅更难阅读,而且有点不太实用。

只是想知道这里是否有人会建议我使用stream()forEach() 或同时使用两者来实现这一目标的更简单、更清晰的方法。

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

提前谢谢你。

【问题讨论】:

  • 显然使用双向地图会更有效。
  • 嗨,patryk。我完全同意你的看法。这就是我发布问题的原因。你能提供解决方案吗?
  • 对我来说,有些气味来自于使用null。发布的答案都提供了null,因为这就是您所要求的。但是既然你已经购买了Optional,为什么还要使用null?将keyOfTheFirst 设为Optional&lt;String&gt; 并执行assertTrue(keyOfTheFirst.isEmpty()) 或其他。
  • @Stuart。当然使用java.util.Optional 是自然的方式,但这不是我想要的。我真正想要的是拥有一个纯功能解决方案,而不需要任何额外的检查/迭代等。这更像是一个理论问题“java 8 可以做到吗?”,因为我找不到我自己把答案贴在这里而不是断定它不能。单元测试只是为了帮助发帖者轻松练习他们的解决方案,而不是别的。
  • @Julian 够了。我在回应上面的评论,“这段代码有味道。糟糕的设计。”该评论可能是真的,但它并不是特别有用。虽然它与您所关注的问题没有直接关系,但我确实发现代码中的一个问题是它使用了null,所以我建议了一个避免null 的替代方案。

标签: java lambda java-8 java-stream optional


【解决方案1】:

若不匹配则返回默认值,使用Optional#orElse

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

【讨论】:

  • 感谢 Misha 和 Doon。您的两个答案都帮助我找出了我想要的解决方案。我选择 Misha's 是最接近它的。要返回 java.lang.String 而不是 java.util.Optional,我所要做的就是调用 orElseGet(new Supplier&lt;String&gt;() { @Override public String get() { return null; } })
  • 如果你真的想用orElseGet代替orElse,你可以使用lambda表达式代替匿名类:.orElseGet(() -&gt; null)
  • 谢谢米莎。这甚至更好,这正是我所追求的:这个问题的纯功能解决方案。 Java 8 震撼!
  • 使用this helper,将是PairStream.from(names).filterValue("Donkey"::equals).keys().findFirst()...
  • 在某些情况下.orElse(null)可以更改为.orElseGet(() -&gt; Iterables.getLast(yourCollection))
【解决方案2】:

来自类似的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());
}

然后你可以选择第一个,如果你愿意的话。请记住,key 是唯一的,value 不是。

编辑: 整个代码(感谢@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());
        }
    }
}

【讨论】:

  • 您可以使用findAny 代替collect 来找到找到的第一个。
  • @PeterLawrey 好提醒!但应该是findFirst,不是吗?
  • 除非你使用parallelStream,否则它会是第一个。
  • 但这与我的第二个解决方案类似。您仍然必须迭代到收集的集合或检查 findFirst 返回的可选值是否存在以获取实际密钥。我在 lambda 内部发生了一些事情,如果有的话会返回我想要的键,否则返回 null 值。
  • @Julian 恐怕你不能走得更远。你无法保证总能找到一把钥匙。
【解决方案3】:

如果您不想使用第三方代码,@Misha 提供的解决方案是最好的解决方案。 My library 有一个特殊的快捷方式 ofKeys 用于我发现这是很常见的任务的情况:

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

【讨论】:

  • 不会过滤一个名为 donkey 的键,而不是对应于一个名为 donkey 的值的键吗?
  • @flup,不,它是对应于名为"Donkey" 的值的键的过滤器。要过滤密钥,您可以使用常用的过滤方法,例如StreamEx.ofKeys(names).filter("Donkey"::equals)
【解决方案4】:

我喜欢老式的:

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

【讨论】:

    【解决方案5】:

    下面是我的代码 sn-p 从地图中获取密钥,

    Map<String,String> pageDetails = new HashMap<String,String>();
    
    public String getAssociatedKey(){
    pageDetails.entrySet().stream().filter( e -> e.getValue().contains("John").findFirst().get().getKey();
    }
    

    【讨论】:

      【解决方案6】:

      为了避免映射条目存在空值时出现空指针异常:

      private String getValueByKey(Map<String, String> map, String key)
      {
              return map.entrySet().stream().filter(e -> 
              StringUtils.equalsIgnoreCase(e.getKey(), key)).findFirst().get()
                      .getValue();
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-09-23
        • 2021-01-30
        • 1970-01-01
        • 2011-06-17
        相关资源
        最近更新 更多