web-dev-qa-db-fra.com

Comparez deux collections Java en utilisant Comparator au lieu de equals ()

Déclaration de problème

J'ai deux collections du même type d'objet que je veux comparer. Dans ce cas, je souhaite les comparer en fonction d'un attribut ne prenant pas en compte equals() pour les objets. Dans mon exemple, j'utilise des collections classées de noms, par exemple:

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.
    }
}

Je souhaite comparer les deux collections pour affirmer que, pour la position i de chaque collection, la weightedRank de chaque nom à cette position correspond à la même valeur. J'ai fait quelques recherches sur Google, mais je n'ai pas trouvé de méthode appropriée dans Commons Collections ou dans une autre 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;
}

Question

Y a-t-il une autre façon de faire cela? Ai-je oublié une méthode évidente de Commons Collections?

En relation

J'ai aussi remarqué cette question sur SO qui est similaire, mais dans ce cas, je pense que remplacer equals() a un peu plus de sens.

Modifier

Quelque chose de très similaire à celui-ci sera dans une version de Apache Commons Collections dans un avenir proche (au moment de la rédaction de cet article). Voir https://issues.Apache.org/jira/browse/COLLECTIONS-446 .

18
Matt Lachman

Je ne suis pas sûr que ce moyen soit réellement meilleur, mais c'est "un autre moyen" ... 

Prenez vos deux collections originales et créez-en de nouvelles contenant un adaptateur pour chaque objet de base. .equals() et .hashCode() doivent être implémentés sur la base de Name.calculateWeightedRank(). Ensuite, vous pouvez utiliser l'égalité de collection normale pour comparer les collections d'adaptateurs.

* Modifier *

Utilisation de la génération hashCode/equals standard d'Eclipse pour la variable Adapter. Votre code appelle simplement adaptCollection sur chacune de vos collections de base, puis List.equals () les deux résultats.

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;
    }

}
1
sharakan

Vous pouvez utiliser la classe Guava Equivalence afin de dissocier les notions de "comparaison" et d '"équivalence". Vous devez toujours écrire votre méthode de comparaison (AFAIK Guava ne l’a pas) qui accepte une sous-classe d’équivalence au lieu du comparateur, mais au moins votre code serait moins déroutant et vous pourriez comparer vos collections en fonction de critères d’équivalence.

L'utilisation d'une collection d'objets enveloppés par l'équivance (voir la méthode wrap dans Equivalence ) serait similaire à la solution basée sur un adaptateur proposée par sharakan, mais l'implémentation d'équivalence serait découplée de l'implémentation d'adaptateur, vous permettant ainsi de l'utiliser facilement. critères d'équivalence multiples.

6
lbalazscs

Vous pouvez utiliser la nouvelle méthode isEqualCollection ajoutée à CollectionUtils depuis la version 4. Cette méthode utilise un mécanisme de comparaison externe fourni par l'implémentation de l'interface Equator. S'il vous plaît, vérifiez ces javadocs: CollectionUtils.isEqualCollection (...) et Equator

4
nndru

EDIT: Ancienne réponse supprimée.

Une autre option consiste à créer une interface appelée Weighted qui pourrait ressembler à ceci:

public interface Weighted {
    int getWeightedRank();
}

Ensuite, demandez à votre classe Name d'implémenter cette interface. Ensuite, vous pourriez changer votre méthode pour ressembler à ceci: 

 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;
}

Ensuite, si vous trouvez des classes supplémentaires qui doivent être pondérées et comparées, vous pouvez les mettre dans votre collection et les comparer. Juste une idée.

0
nattyddubbs