【问题标题】:How can I compare two array lists for equality with a custom comparator?如何使用自定义比较器比较两个数组列表是否相等?
【发布时间】:2015-05-07 16:07:48
【问题描述】:

具体来说,我有两个列表:

List<SystemUserWithNameAndId> list1;
List<SystemUserWithNameAndId> list2;

我想检查它们是否包含相同的系统用户并且订购不是问题。我尝试使用比较器先对它们进行排序,然后使用列表的 equals() 方法检查它们是否相等。但我不想覆盖 SystemUserWithNameAndId 的 equals 方法,我想知道是否可以使用我为排序创建的比较器或类似的比较器来检查相等性,而无需在排序后显式迭代列表。

Comparator<SystemUserWithNameAndId> systemUserComparator = new Comparator<SystemUserWithNameAndId>()
    {

        @Override
        public int compare(SystemUserWithNameAndId systemUser1, SystemUserWithNameAndId systemUser2)
        {
            final int systemUserId1 = systemUser1.getSystemUserId();
            final int systemUserId2 = systemUser2.getSystemUserId();

            return systemUserId1 == systemUserId2 
                    ? 0
                    : systemUserId1 - systemUserId2;
        }
    };

    Collections.sort(systemUsers1, systemUserComparator);
    Collections.sort(systemUsers2, systemUserComparator);

    return systemUsers1.equals(systemUsers2);

理想情况下,我想说,

CollectionUtils.isEqualCollections(systemUsers1, systemUsers2, someCustomComparator);

【问题讨论】:

  • 为什么不想覆盖SystemUserWithNameAndId.equals()?你已经覆盖它了吗?
  • 或者你可以做一个扩展 List 并覆盖 equals 的 SystemUsersList,然后你可以调用:list1.equals(list2);
  • 我发布的代码是测试类的一部分,我比较两个 SystemUserWithNameAndId 对象的方式不是比较它们的默认方式。
  • 您使用的是 Java 8 吗?
  • @Radiodef 我会回答,但问题尚不清楚。 OP 是否想要根据Comparator 测试两个列表是否包含相同的元素(正如 JBNizet 假设的那样),或者Comparator 只是测试两个列表是否包含与@相关的相同元素的一种方法987654328@?在问题中,OP 使用Comparator 对列表进行排序,然后通过将元素与equals() 进行比较来测试列表是否相同,这没有任何意义。

标签: java generics collections comparison


【解决方案1】:

只需实现迭代的方法,并在每次需要时重用它:

public static <T> boolean areEqualIgnoringOrder(List<T> list1, List<T> list2, Comparator<? super T> comparator) {

    // if not the same size, lists are not equal
    if (list1.size() != list2.size()) {
        return false;
    }

    // create sorted copies to avoid modifying the original lists
    List<T> copy1 = new ArrayList<>(list1);
    List<T> copy2 = new ArrayList<>(list2);

    Collections.sort(copy1, comparator);
    Collections.sort(copy2, comparator);

    // iterate through the elements and compare them one by one using
    // the provided comparator.
    Iterator<T> it1 = copy1.iterator();
    Iterator<T> it2 = copy2.iterator();
    while (it1.hasNext()) {
        T t1 = it1.next();
        T t2 = it2.next();
        if (comparator.compare(t1, t2) != 0) {
            // as soon as a difference is found, stop looping
            return false;
        }
    }
    return true;
}

【讨论】:

  • 他说“排序后没有显式迭代列表” - 但我不知道如何实现这一点(也许将对象存储在 @ 987654322@左右,但为此,没有提供足够的信息)
  • 一旦你编写了这个通用的可重用方法,你就永远不会“显式循环”。您通过调用该方法“隐式循环”。没有循环就无法比较列表的元素。而且由于JDK没有提供这样的实用方法,所以需要自己写一个。
  • @JBNizet 这正是我希望我可以避免的,但如果你确定没有实用功能可以做到这一点,我将不得不求助于你所推荐的。跨度>
  • 你必须遍历最大的集合,否则 areEqualIgnoringOrder 会给出不好的结果。示例:列表 a = [1,2,3],列表 b = [1,2,3,4]。 areEqualIgnoringOrder(a,b,comparator) 将返回 true。
  • @TomasBartalos 该方法做的第一件事是比较列表的大小,这必须相等。
【解决方案2】:

这是解决问题的 Java 8 方法。首先确保列表长度相等:

List<SystemUserWithNameAndId> list1 = ... ;
List<SystemUserWithNameAndId> list2 = ... ;

if (list1.size() != list2.size()) {
    return false;
}

现在使用新的比较器实用程序构建一个比较器。这个想法是,大多数比较器不是为比较器编写自定义逻辑,而是通过从中提取键来比较两个对象,然后比较键。这就是它的作用。

Comparator<SystemUserWithNameAndId> comp =
    Comparator.comparingInt(SystemUserWithNameAndId::getSystemUserId);

对列表进行排序。当然,如果您不希望您的函数具有对其输入进行排序的副作用,您可能希望在排序之前进行复制。如果您的输入列表不是随机访问的(现在谁使用LinkedList?)您可能还想将它们复制到ArrayLists 以方便随机访问。

list1.sort(comp);
list2.sort(comp);

在列表的索引上运行一个流,在每一对上调用比较器。如果根据此比较器元素相等,则比较器返回 0。如果所有元素对都是如此,则列表是相等的。

return IntStream.range(0, list1.size())
    .allMatch(i -> comp.compare(list1.get(i), list2.get(i)) == 0);

【讨论】:

  • 感谢您抽出宝贵的时间来回答,我还没有使用 Java 8,但是当我进行切换时,我会记住这一点。一个小警告,我想使用不同的比较器进行排序和检查是否相等,因为标准不一样。
  • @BruceWayne 没问题。您可以轻松地在传递给allMatch 的谓词中使用equals 或不同的比较器。
  • 呃...虽然问题是关于比较器的,但如果只需要相等检查,为什么不直接使用预测器呢?
猜你喜欢
  • 1970-01-01
  • 2016-03-06
  • 1970-01-01
  • 1970-01-01
  • 2014-09-20
  • 2013-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多