【问题标题】:Java Map, filter with values propertiesJava Map,使用值属性过滤
【发布时间】:2012-06-24 23:59:16
【问题描述】:

我有一个

TreeMap resMap new TreeMap<String, Map<String, String>>(); 

我想过滤并只保留值包含已知对的条目,比如说 ('mike' => 'jordan'),并避免像下面这样的循环

在我包含的库 apache.commons 和 google.common 中是否有一个过滤器方法(这可能也会做一个循环,但至少它不那么冗长

for (Entry<String, TreeMap<String, String>> el : resMap.entrySet()){
    if (el.getValue().get("mike").equals("jordan")){
        //
    }
}

【问题讨论】:

  • 你需要某种 DotNet 在 Java 中的 LINQ 等价物...
  • 为什么要避免循环?你使用的任何东西都像是为你使用了一个循环。
  • 次要评论:在等号中切换顺序,因为 get("mike") 可能返回 null。即if ("jordan".equals(el.getValue().get("mike"))
  • @PeterLawrey,“你使用的任何东西都像是为你使用循环。”那是毫无意义的争论。这个类比可能有助于理解为什么会这样:“我想避免 goto 指令。$lang 是否支持正确的循环?” “你为什么想要循环?无论如何,他们很可能在内部使用 goto。”像这样,你可以一直到原始位。
  • @missingfaktor 避免使用goto 的原因有很多,但因为不喜欢它们而避免使用它们并不是其中之一。

标签: java map filter


【解决方案1】:

看看GuavaPredicatesFunctions

【讨论】:

  • 自然!他们还从用户代码中删除了重复的此类循环。
  • 并将它们替换为比它们替换的循环更长的谓词。 ;)
  • @PeterLawrey 但是谁在乎性能是否正常。它使您的基础更小,并且在语义上更容易理解。它还可以让您抽象地处理数据,让实现来决定如何/过滤什么。为了清楚起见,我也认为 OP 提出了错误的问题,这不是要走的路,但它并非没有优点
  • 好吧,公平地说,这些大多返回视图
  • @Bohemian 总是让我感到困惑的是需要用更复杂的东西替换循环。通常,循环是执行此操作的最短和最干净的方法。它可以稍微快一点,但正如你所说,大多数时候这并不重要。
【解决方案2】:

您可以使用来自 Guava 和 Predicate 界面的过滤器。

Predicate<T> yourFilter = new Predicate<T>() {
    public boolean apply(T o) {
        // your filter
    }
};

所以,简单的例子是:

Predicate<Integer> evenFilter = new Predicate<Integer>() {
    public boolean apply(Integer i) {
        return (i % 2 == 0);
    }
};

Map<Integer, Integer> map = new HashMap<Integer, Integer>();

Map<Integer, Integer> evenMap = Maps.filterValues(map, evenFilter);

【讨论】:

  • 我认为,尽管 guava 过滤器无论是大地图还是小地图都需要相同的时间,但对小地图使用迭代会提供更好的性能。
【解决方案3】:

与其强制您的客户端代码使用过滤器/循环,不如将您需要的内容构建到您的类的 API 中:

public class MyClass {

    private TreeMap resMap new TreeMap<String, Map<String, String>>();

    public void filter(String key, String value) {
        // Some impl here. Either your loop or the guava approach
    }
}

顺便说一句,如果您使用循环,请考虑更改为:

for (Iterator<Map.Entry<String, TreeMap<String, String>>> i = resMap.entrySet().iterator(); i.hasNext();) {
    Map.Entry<String, TreeMap<String, String>> entry = i.next();
    if (value.equals(entry.getValue().get(key))) {
        i.remove();
    }
}

循环的变化是:

  • 更改了 equals 的顺序以避免 NPE
  • 使用iterator 允许直接删除条目

即使您没有类,您也可以轻松地将其封装在实用程序类的静态方法中,也可以轻松地对其进行参数化以使用任何嵌套映射:

public static <K1, K2, V> void filter(Map<K1, Map<K2, V>> map, K2 key, V value) {
    // Some impl here
}

这是静态方法的非番石榴实现:

for (Iterator<Map.Entry<K1, Map<K2, V>>> i = map.entrySet().iterator(); i.hasNext();) {
    Map.Entry<K1, Map<K2, V>> entry = i.next();
    if (value.equals(entry.getValue().get(key))) {
        i.remove();
    }
}

【讨论】:

  • @EugeneKuleshov 很好,但这是一个实现选择。这个答案的主要目的是提供一个 API,因此客户端代码不需要了解 guava/loops 等。我已经编辑了答案来说明这一点。
  • @EugeneKuleshov 与其重新发明循环,不如使用作为语言结构的循环。 ;)
  • @PeterLawrey 你知道你的评论可以被循环替换吗?
  • @EugeneKuleshov 你的意思是我的陈述是自我参照的吗?
【解决方案4】:

这里有两个例子。两者都根据值属性中的匹配打印键。

private static void printMatchingEntriesUsingALoop(Map<String, Map<String, String>> resMap, String key, String value) {
    for (Map.Entry<String, Map<String, String>> entry : resMap.entrySet())
        if (value.equals(entry.getValue().get(key)))
            System.out.println(entry.getKey());
}

private static void printMatchingEntriesUsingGuava(Map<String, Map<String, String>> resMap, final String key, final String value) {
    Predicate<Map<String, String>> keyValueMatch = 
    new Predicate<Map<String, String>>() {
        @Override
        public boolean apply(@Nullable Map<String, String> stringStringMap) {
            return value.equals(stringStringMap.get(key));
        }
    };

    Maps.EntryTransformer<String, Map<String, String>, Void> printKeys = 
    new Maps.EntryTransformer<String, Map<String, String>, Void>() {
        @Override
        public Void transformEntry(@Nullable String s, 
                 @Nullable Map<String, String> stringStringMap) {
            System.out.println(s);
            return null;
        }
    };

    Maps.transformEntries(Maps.filterValues(resMap, keyValueMatch), printKeys);
}

public static void main(String... args) {
    Map<String, Map<String, String>> resMap = new TreeMap<String, Map<String, String>>();
    printMatchingEntriesUsingALoop(resMap, "first", "mike");
    printMatchingEntriesUsingGuava(resMap, "first", "mike");
}

一个使用循环,一个使用 Guava。

虽然第一个性能更好,但您应该真正决定哪个最容易理解和维护。

来自@missingfaktor 的一些建议。您必须使用自己的判断力,但他很好地强调了一些问题。

  1. 很多代码重复。
  2. 特殊情况处理。
  3. 更复杂的圈。
  4. 由于前三个项目符号,出错的可能性更大。
  5. 难以遵循代码。

假设您是一名必须支持此软件的新开发人员。你更愿意面对哪个?

【讨论】:

    【解决方案5】:

    您可以使用 java 8 和流过滤地图。此过程的第一步是使用entrySet().stream() 转换为流。这会给你一个Stream&lt;Map.Entry&lt;String, TreeMap&lt;String, String&gt;&gt;。然后,您可以使用filter(...) 过滤列表。过滤时,如果传入值应包含在过滤结果中,则应返回 true。过滤结果后,您可以使用 foreach 循环遍历最终结果。

    最终结果将如下所示:

    resMap.entrySet().stream()
          .filter(e -> el.getValue().get("mike").equals("jordan"))
          .foreach(e -> {
            // Do something with your entry here
          });
    

    【讨论】:

      【解决方案6】:

      From @Ferrybig answer in this post.

      您可以为此使用 Java 8 方法 Collection.removeIf

      map.values().removeIf(Object o -> o.get("mike").equals("jordan"));
      

      这删除了与谓词匹配的所有值。

      Online demo

      这是因为为 HashMap 调用 .values() 返回一个集合,该集合将修改委托给 HashMap 本身,这意味着我们对 removeIf() 的调用实际上改变了 HashMap(这不适用于所有 java Map )

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-11-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-05-30
        • 2022-01-17
        • 1970-01-01
        • 2015-09-14
        相关资源
        最近更新 更多