【问题标题】:Comparing two maps比较两张地图
【发布时间】:2014-09-08 23:25:51
【问题描述】:

我有两张地图声明为Map<String, Object>。这里的Object 可能是另一个Map<String, Object>(等等)。我想在不知道深度的情况下检查两张地图是否完全相同。我可以比较每个地图上调用的toString() 的输出而不是使用递归吗?或者有没有更简单的方法来比较地图?

【问题讨论】:

  • 这对别人来说可能很愚蠢,但对你来说很重要,所以它最终变得可敬和重要。
  • 如果地图条目以不同的顺序出现,使用toString()会失败,这是完全可能的。
  • @user3580294 如果一张地图是TreeMap,另一张是HashMap(在我的回答中包含示例:-)。
  • .toString() 本身会有递归行为。你为什么讨厌它?
  • @maythesource.com 是的,在我发布评论后不久就意识到我的评论存在缺陷,但由于某种原因没有收到您回复的通知......无论如何,我最初认为那个 OP 正在谈论 HashMap 实例,因为我的阅读能力很糟糕,但后来意识到即使对于那些我所说的也不是真的。在那之后,我意识到 OP 只有 Map 实例。无论如何,你是对的。

标签: java dictionary


【解决方案1】:

只要您在映射中包含的每个键和值上覆盖 equals(),那么 m1.equals(m2) 应该可以可靠地检查映射是否相等。

按照您的建议,比较每张地图的toString() 也可以获得相同的结果,但使用equals() 是一种更直观的方法。

可能不是你的具体情况,但是如果你在map中存储数组,可能会有点棘手,因为它们必须逐个值比较,或者使用Arrays.equals()。有关此的更多详细信息,请参阅here

【讨论】:

    【解决方案2】:

    快速解答

    您应该使用equals 方法,因为这是为了执行您想要的比较而实现的。 toString() 本身就像 equals 一样使用迭代器,但它是一种效率较低的方法。此外,正如@Teepeemm 指出的那样,toString 受元素顺序(基本上是迭代器返回顺序)的影响,因此不能保证为 2 个不同的地图提供相同的输出(特别是如果我们比较两个不同的地图)。

    注意/警告:您的问题和我的回答假定实现地图接口的类尊重预期的toStringequals 行为。默认的 java 类会这样做,但需要检查自定义映射类以验证预期的行为。

    见:http://docs.oracle.com/javase/7/docs/api/java/util/Map.html

    boolean equals(Object o)
    

    比较指定对象与此映射是否相等。返回真 如果给定的对象也是一个地图并且两个地图表示相同 映射。更正式地说,两个映射 m1 和 m2 表示相同的 映射如果 m1.entrySet().equals(m2.entrySet())。这确保了 equals 方法可以在不同的实现中正常工作 地图界面。

    Java 源码中的实现 (java.util.AbstractMap)

    此外,java 本身负责遍历所有元素并进行比较,因此您不必这样做。看看AbstractMap 的实现,它被HashMap 等类使用:

     // Comparison and hashing
    
        /**
         * Compares the specified object with this map for equality.  Returns
         * <tt>true</tt> if the given object is also a map and the two maps
         * represent the same mappings.  More formally, two maps <tt>m1</tt> and
         * <tt>m2</tt> represent the same mappings if
         * <tt>m1.entrySet().equals(m2.entrySet())</tt>.  This ensures that the
         * <tt>equals</tt> method works properly across different implementations
         * of the <tt>Map</tt> interface.
         *
         * <p>This implementation first checks if the specified object is this map;
         * if so it returns <tt>true</tt>.  Then, it checks if the specified
         * object is a map whose size is identical to the size of this map; if
         * not, it returns <tt>false</tt>.  If so, it iterates over this map's
         * <tt>entrySet</tt> collection, and checks that the specified map
         * contains each mapping that this map contains.  If the specified map
         * fails to contain such a mapping, <tt>false</tt> is returned.  If the
         * iteration completes, <tt>true</tt> is returned.
         *
         * @param o object to be compared for equality with this map
         * @return <tt>true</tt> if the specified object is equal to this map
         */
        public boolean equals(Object o) {
            if (o == this)
                return true;
    
            if (!(o instanceof Map))
                return false;
            Map<K,V> m = (Map<K,V>) o;
            if (m.size() != size())
                return false;
    
            try {
                Iterator<Entry<K,V>> i = entrySet().iterator();
                while (i.hasNext()) {
                    Entry<K,V> e = i.next();
                    K key = e.getKey();
                    V value = e.getValue();
                    if (value == null) {
                        if (!(m.get(key)==null && m.containsKey(key)))
                            return false;
                    } else {
                        if (!value.equals(m.get(key)))
                            return false;
                    }
                }
            } catch (ClassCastException unused) {
                return false;
            } catch (NullPointerException unused) {
                return false;
            }
    
            return true;
        }
    

    比较两种不同类型的地图

    toString 在比较 TreeMapHashMap 时失败得很惨,尽管 equals 确实比较内容正确。

    代码:

    public static void main(String args[]) {
    HashMap<String, Object> map = new HashMap<String, Object>();
    map.put("2", "whatever2");
    map.put("1", "whatever1");
    TreeMap<String, Object> map2 = new TreeMap<String, Object>();
    map2.put("2", "whatever2");
    map2.put("1", "whatever1");
    
    System.out.println("Are maps equal (using equals):" + map.equals(map2));
    System.out.println("Are maps equal (using toString().equals()):"
            + map.toString().equals(map2.toString()));
    
    System.out.println("Map1:"+map.toString());
    System.out.println("Map2:"+map2.toString());
    }
    

    输出:

    Are maps equal (using equals):true
    Are maps equal (using toString().equals()):false
    Map1:{2=whatever2, 1=whatever1}
    Map2:{1=whatever1, 2=whatever2}
    

    【讨论】:

    • lambda 表达式能让条件更好吗?
    • @Kick Buttowski 你是什么意思?
    • 你说回答这个问题很复杂,因为我尝试在 Java 中学习 Lambda 表达式,我想知道使用 lambda 表达式是否会有所帮助?
    • @KickButtowski 我还没有使用 Java 8,但我确信如果我将地图展平它会变得微不足道:)
    • 需要强调的是,即使两个map的类型相同,即都是HashMaps,也不能保证迭代顺序相同,所以比较toString()结果会失败。遇到的顺序是一个实现细节,它取决于键的哈希码当前容量(即使大小相同,也可能不同),此外,条目存储在相同的数组元素中(又名有哈希冲突)按照地图的历史的顺序链接,即插入顺序或是否删除了其他键等。
    【解决方案3】:

    比较两个地图:

    声明:

    enum Activity{
        ADDED,
        REMOVED,
        MODIFIED
    }
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    static class FileStateRow {
        String key;
        String value;
        Activity activity;
    }
    
    BiFunction<Map<String, Object>, Map<String, Object>, Map<String, FileStateRow>> 
    mapCompare = (newMap, oldMap) -> {
        Map<String, FileStateRow> resMap = new HashMap<>();
        newMap.forEach((k, v) -> {
            if (!oldMap.containsKey(k)) {
                System.out.println("newMap key:" + k + " is missing in oldMap - ADDED");
                resMap.put(k, new FileStateRow(k, (String) v, Activity.ADDED));
            } else {
                if (oldMap.get(k) != null && !oldMap.get(k).equals(v)) {
                    System.out.println("newMap value change for key:" + k + ", old:" + oldMap.get(k) + ", new " + v);
                    resMap.put(k, new FileStateRow(k, (String) v, Activity.MODIFIED));
                }
            }
        }); 
    
        oldMap.forEach((k, v) -> {
            if (!newMap.containsKey(k)) {
                System.out.println("newMap key:" + k + " is missing in oldMap");
                resMap.put(k, new FileStateRow(k, (String) v, Activity.REMOVED));
            }
        }); 
        return resMap;
    };
    

    使用:

    Map<String, Object> map1 = .. // initiate and put values in..
    Map<String, Object> map2 = .. // initiate and put values in..
    
    // compare...
    Map<String, FileStateRow> res = mapCompare.apply(map1, map2);
    
    // print results
    res.forEach((k, v) -> {
         System.out.println("key:" + k + ",  value " + v);
    });
    

    由 yl.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-02-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多