使用Comparator而不是equals()比较两个Java集合

时间:2013-02-26 16:55:41

标签: java collections equals comparator

问题陈述

我有两个与我想要比较的相同类型对象的集合。在这种情况下,我想基于不考虑对象equals()的属性来比较它们。在我的示例中,我使用名称的排名集合,例如:

public class Name {
    private String name;
    private int weightedRank;

    //getters & setters

    @Override
    public boolean equals(Object obj) {
        return this.name.equals(obj.name); //Naive implementation just to show
                                           //equals is based on the name field.
    }
}

我想比较两个集合来断言,对于每个集合中的位置i,该位置的每个名称的weightedRank是相同的值。我做了一些谷歌搜索,但没有在Commons Collections或任何其他API中找到合适的方法,所以我想出了以下内容:

public <T> boolean comparatorEquals(Collection<T> col1, Collection<T> col2,
        Comparator<T> c)
{
    if (col1 == null)
        return col2 == null;
    if (col2 == null) 
        return false;

    if (col1.size() != col2.size())
        return false;

    Iterator<T> i1 = col1.iterator(), i2 = col2.iterator();

    while(i1.hasNext() && i2.hasNext()) {
        if (c.compare(i1.next(), i2.next()) != 0) {
            return false;
        }
    }

    return true;
}

问题

还有其他办法吗?我是否错过了Commons Collections的明显方法?

相关

我也在SO上发现了this question,虽然在这种情况下,我认为覆盖equals()会更有意义。

修改

与此类似的东西将在不久的将来(在撰写本文时)发布Apache Commons Collections。请参阅https://issues.apache.org/jira/browse/COLLECTIONS-446

4 个答案:

答案 0 :(得分:6)

您可以使用Guava Equivalence类来解耦“比较”和“等价”的概念。您仍然需要编写比较方法(AFAIK Guava没有它)接受Equivalence子类而不是Comparator,但至少您的代码会更少混淆,您可以根据任何等价标准比较您的集合。 / p>

使用等价包装对象的集合(参见wrap method in Equivalence)类似于sharakan提出的基于适配器的解决方案,但是等价实现将与适配器实现分离,允许您轻松使用多重等价标准。

答案 1 :(得分:4)

从版本4开始,您可以使用添加到isEqualCollection的新CollectionUtils方法。此方法使用Equator接口实现提供的外部比较机制。请检查此javadocs:CollectionUtils.isEqualCollection(...)Equator

答案 2 :(得分:1)

我不确定这种方式实际上是否更好,但这是“另一种方式”......

获取原始的两个集合,并为每个基础对象创建包含适配器的新集合。适配器应基于.equals()实施.hashCode()Name.calculateWeightedRank()。然后,您可以使用常规Collection等式来比较Adapters的集合。

*编辑*

Adapter使用Eclipse的标准hashCode / equals生成。你的代码只会在每个基本集合上调用adaptCollection,然后在List.equals()中调用两个结果。

public class Adapter {

    public List<Adapter> adaptCollection(List<Name> names) {
        List<Adapter> adapters = new ArrayList<Adapter>(names.size());

        for (Name name : names) {
            adapters.add(new Adapter(name));
        }

        return adapters;
    }


    private final int name;

    public Adapter(Name name) {
        this.name = name.getWeightedResult();
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + name;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Adapter other = (Adapter) obj;
        if (name != other.name)
            return false;
        return true;
    }

}

答案 3 :(得分:0)

编辑:删除了旧答案。

您拥有的另一个选项是创建一个名为Weighted的界面,如下所示:

public interface Weighted {
    int getWeightedRank();
}

然后让您的Name类实现此接口。然后你可以改变你的方法:

 public <T extends Weighted> boolean weightedEquals(Collection<T> col1, Collection<T> col2)
{
    if (col1 == null)
      return col2 == null;
     if (col2 == null) 
      return false;

  if (col1.size() != col2.size())
      return false;

  Iterator<T> i1 = col1.iterator(), i2 = col2.iterator();

  while(i1.hasNext() && i2.hasNext()) {
      if (i1.next().getWeightedRank() != i2.next().getWeightedRank()) {
          return false;
      }
  }

  return true;
}

然后,当您发现需要加权和比较的其他类时,您可以将它们放入您的集合中,并且它们也可以相互比较。只是一个想法。