如何检查 Java 中 LinkedHashMap 的相等性 - 还要考虑插入顺序?

How to check equality of LinkedHashMaps in Java - also taking the insertion-order into account?

我想检查 Java 中两个 LinkedHashMap 的相等性。

equals() 方法位于 AbstractMap 中,仅检查比较列表中是否存在具有相同键和值的条目。因此,不检查插入顺序:

package com.Whosebug.tests;

import java.util.LinkedHashMap;

public class LinkedHashMapEqualsTest {

  public static void main(String[] args) {
    LinkedHashMap<String, String> lhm1 = new LinkedHashMap<String, String>();
    lhm1.put("A", "1");
    lhm1.put("B", "2");
    lhm1.put("C", "3");
    LinkedHashMap<String, String> lhm2 = new LinkedHashMap<String, String>();
    lhm2.put("A", "1");
    lhm2.put("B", "2");
    lhm2.put("C", "3");
    LinkedHashMap<String, String> lhm3 = new LinkedHashMap<String, String>();
    lhm3.put("A", "1");
    lhm3.put("C", "3");
    lhm3.put("B", "2");
    LinkedHashMap<String, String> lhm4 = new LinkedHashMap<String, String>();
    lhm4.put("A", "1");
    lhm4.put("B", "2");
    LinkedHashMap<String, String> lhm5 = new LinkedHashMap<String, String>();
    lhm5.put("A", "2");
    lhm5.put("B", "2");
    lhm5.put("C", "3");

    if(lhm1.equals(lhm1)) {
      System.out.println("Positive control. - SUCCESS");
    }
    if(lhm1.equals(lhm2)) {
      System.out.println("lhm1 does equal lhm2; as expected. - SUCCESS");
    }
    if(lhm1.equals(lhm3)) {
      System.out.println("lhm1 does equal lhm3, although the insert-order is different.");
    }
    if(!lhm1.equals(lhm4)) {
      System.out.println("Negative control 1. - SUCCESS");
    }
    if(!lhm1.equals(lhm5)) {
      System.out.println("Negative control 2. - SUCCESS");
    }

  }

}

我如何检查两个比较列表的插入顺序是否相同?

无需任何扩展即可执行此操作的天真方法是使用 toString() 输出:

public static <K,V> boolean equalConsideringInsertionOrder(
  Map<K,V> left, Map<K,V> right){

  return left.toString().equals(right.toString());
}

但正如评论中指出的那样,这个版本有些问题。 toString() 不是规范格式,因此不应该那样使用。

一个更详细、更高效、更正确的版本应该是这样的:

public static <K, V> boolean equalConsideringInsertionOrder(
        Map<K, V> left, Map<K, V> right) {

    Iterator<Map.Entry<K, V>> leftIterator = left.entrySet().iterator();
    Iterator<Map.Entry<K, V>> rightIterator = right.entrySet().iterator();
    while (leftIterator.hasNext() && rightIterator.hasNext()) {
        Map.Entry<K, V> leftEntry = leftIterator.next();
        Map.Entry<K, V> rightEntry = rightIterator.next();
        if (!Objects.equals(leftEntry.getKey(), rightEntry.getKey())
                || !Objects.equals(leftEntry.getValue(),rightEntry.getValue())) {
            return false;
        }
    }
    return !leftIterator.hasNext() && !rightIterator.hasNext();
}

我可能不会覆盖 LinkedHashMapequals(),但会提供一个辅助方法,例如像这样(灵感来自 AbstractList#equals(...)):

public static <K, V> boolean linkedEquals( LinkedHashMap<K, V> left, LinkedHashMap<K, V> right) {
  Iterator<Entry<K, V>> leftItr = left.entrySet().iterator();
  Iterator<Entry<K, V>> rightItr = right.entrySet().iterator();

  while ( leftItr.hasNext() && rightItr.hasNext()) {
     Entry<K, V> leftEntry = leftItr.next();
     Entry<K, V> rightEntry = rightItr.next();

     //AbstractList does null checks here but for maps we can assume you never get null entries
     if (! leftEntry.equals(rightEntry))
         return false;
  }
  return !(leftItr.hasNext() || rightItr.hasNext());
} 

然后你像if( linkedEquals(lhm1, lhm3) )一样使用它。

编辑:

根据要求,另一种产生较低性能(由于多次不必要的迭代)但需要编写较少代码的方法是将条目集转换为列表并进行比较,例如像这样:

if( new ArrayList<>(lhm1.entrySet()).equals(new ArrayList<>(lhm3.entrySet()) ) { ... }