Est-il possible en java de faire quelque chose comme Comparator mais pour implémenter des equals () et hashCode () personnalisés

J’ai un tableau d’objects et je veux le concaténer avec un autre tableau d’objects, à l’exception des objects qui ont le même identifiant. Les objects sont utilisés à de nombreux endroits du système et n’ont pas de code de hachage ou d’équivalent. Donc, je ne veux pas implémenter hashCode() et equals() , car j’ai peur de casser quelque chose quelque part dans le système où ces objects sont utilisés et je n’en sais rien.

Je veux mettre tous ces objects dans un ensemble, mais faire en sorte que les objects utilisent un hashCode() et equals() . Quelque chose comme Comparator personnalisé, mais égal.

Oui, il est possible de faire une telle chose. Mais cela ne vous permettra pas de placer vos objects dans un HashMap, un HashSet, etc. C’est parce que les classes de collection standard attendent des objects clés qu’ils fournissent les méthodes d’ equals et hashCode . (C’est comme ça qu’ils sont conçus pour fonctionner …)

Alternatives:

  1. Implémentez une classe wrapper qui contient une instance de la classe réelle et fournit sa propre implémentation de equals et hashCode .

  2. Implémentez vos propres classes basées sur la table de hachage qui peuvent utiliser un object “hashable” pour fournir des fonctionnalités égales et des codes de hachage.

  3. Mordez la puce et implémentez equals et hashCode remplace les classes pertinentes.

En fait, la troisième option est probablement la meilleure, car votre base de code doit probablement utiliser une notion cohérente de la signification de ces objects. D’autres éléments suggèrent que votre code nécessite une révision. Par exemple, le fait qu’il utilise actuellement un tableau d’objects au lieu d’une implémentation Set pour représenter ce qui est apparemment supposé être un ensemble.

D’un autre côté, il y a peut-être / est une raison de performance réelle (ou imaginaire) pour la mise en œuvre actuelle; par exemple, réduction de l’utilisation de la mémoire. Dans ce cas, vous devriez probablement écrire un tas de méthodes d’aide pour effectuer des opérations telles que la concaténation de 2 ensembles représentés sous forme de tableaux.

90% du temps lorsqu’un utilisateur souhaite une relation d’équivalence, il existe déjà une solution plus simple. Vous voulez dédupliquer un tas de choses basées sur des identifiants uniquement? Pouvez-vous simplement les mettre tous dans une carte avec les identifiants comme clés, puis obtenir la collection values() de celle-ci?

HashingStrategy est le concept que vous recherchez. C’est une interface de stratégie qui vous permet de définir des implémentations personnalisées d’égal à égal et de hashcode.

 public interface HashingStrategy { int computeHashCode(E object); boolean equals(E object1, E object2); } 

Comme d’autres l’ont fait remarquer, vous ne pouvez pas utiliser une HashingStrategy de HashingStrategy avec HashSet ou HashMap . Eclipse Collections inclut un ensemble appelé UnifiedSetWithHashingStrategy et une carte appelée UnifiedMapWithHashingStrategy .

Regardons un exemple. Voici une classe de Data simple que nous pouvons utiliser dans UnifiedSetWithHashingStrategy .

 public class Data { private final int id; public Data(int id) { this.id = id; } public int getId() { return id; } // No equals or hashcode } 

Voici comment vous pouvez configurer un UnifiedSetWithHashingStrategy et l’utiliser.

 java.util.Set set = new UnifiedSetWithHashingStrategy<>(HashingStrategies.fromFunction(Data::getId)); Assert.assertTrue(set.add(new Data(1))); // contains returns true even without hashcode and equals Assert.assertTrue(set.contains(new Data(1))); // Second call to add() doesn't do anything and returns false Assert.assertFalse(set.add(new Data(1))); 

Pourquoi ne pas simplement utiliser une Map ? UnifiedSetWithHashingStrategy utilise la moitié de la mémoire d’une UnifiedMap et un quart la mémoire d’un HashMap . Et parfois, vous n’avez pas de clé pratique et vous devez créer une clé synthétique, comme un tuple. Cela peut gaspiller plus de mémoire.

Comment effectuons-nous des recherches? Rappelez-vous que Sets a contains() , mais pas get() . UnifiedSetWithHashingStrategy implémente Pool en plus de MutableSet , il implémente également une forme de get() .

Note: Je suis un committer pour les collections Eclipse.

Bien sûr, vous pouvez créer un object externe fournissant une comparaison d’égalité et un HashCode. Mais les collections intégrées de Java n’utilisent pas un tel object pour leurs comparaisons / recherches.

J’ai déjà créé une interface comme celle-ci dans ma collection de paquets (tout juste traduite en anglais):

 public interface HashableEquivalenceRelation { /** * Returns true if two objects are considered equal. * * This should form an equivalence relation, meaning it * should fulfill these properties: * 
    *
  • Reflexivity: {@code areEqual(o, o)} * should always return true.
  • *
  • Symmetry: {@code areEqual(o1,o2) == areEqual(o2,o1)} * for all objects o1 and o2
  • *
  • Transitivity: If {@code areEqual(o1, o2)} and {@code areEqual(o2,o3)}, * then {@code areEqual(o1,o3}} should hold too.
  • *
* Additionally, the relation should be temporary consistent, ie the * result of this method for the same two objects should not change as * long as the objects do not change significantly (the precise meaning of * change significantly is dependent on the implementation). * * Also, if {@code areEqual(o1, o2)} holds true, then {@code hashCode(o1) == hashCode(o2)} * must be true too. */ public boolean areEqual(Object o1, Object o2); /** * Returns a hashCode for an arbitrary object. * * This should be temporary consistent, ie the result for the same * objects should not change as long as the object does not change significantly * (with change significantly having the same meaning as for {@link areEqual}). * * Also, if {@code areEqual(o1, o2)} holds true, then {@code hashCode(o1) == hashCode(o2)} * must be true too. */ public int hashCode(Object o); }

Si j’avais un groupe d’interfaces CustomCollection , CustomSet , CustomList , CustomMap , etc. défini comme les interfaces dans java.util , mais en utilisant une telle relation d’équivalence pour toutes les méthodes au lieu de la relation Object.equals fournie par Object.equals . J’avais aussi quelques implémentations par défaut:

 /** * The equivalence relation induced by Object#equals. */ public final static EquivalenceRelation DEFAULT = new EquivalenceRelation() { public boolean areEqual(Object o1, Object o2) { return o1 == o2 || o1 != null && o1.equals(o2); } public int hashCode(Object ob) { return ob == null? 0 : ob.hashCode(); } public Ssortingng toSsortingng() { return ""; } }; /** * The equivalence relation induced by {@code ==}. * (The hashCode used is {@link System#identityHashCode}.) */ public final static EquivalenceRelation IDENTITY = new EquivalenceRelation() { public boolean areEqual(Object o1, Object o2) { return o1 == o2; } public int hashCode(Object ob) { return System.identityHashCode(ob); } public Ssortingng toSsortingng() { return ""; } }; /** * The all-relation: every object is equivalent to every other one. */ public final static EquivalenceRelation ALL = new EquivalenceRelation() { public boolean areEqual(Object o1, Object o2) { return true; } public int hashCode(Object ob) { return 0; } public Ssortingng toSsortingng() { return ""; } }; /** * An equivalence relation partitioning the references * in two groups: the null reference and any other reference. */ public final static EquivalenceRelation NULL_OR_NOT_NULL = new EquivalenceRelation() { public boolean areEqual(Object o1, Object o2) { return (o1 == null && o2 == null) || (o1 != null && o2 != null); } public int hashCode(Object o) { return o == null ? 0 : 1; } public Ssortingng toSsortingng() { return ""; } }; /** * Two objects are equivalent if they are of the same (actual) class. */ public final static EquivalenceRelation SAME_CLASS = new EquivalenceRelation() { public boolean areEqual(Object o1, Object o2) { return o1 == o2 || o1 != null && o2 != null && o1.getClass() == o2.getClass(); } public int hashCode(Object o) { return o == null ? 0 : o.getClass().hashCode(); } public Ssortingng toSsortingng() { return ""; } }; /** * Compares ssortingngs ignoring case. * Other objects give a {@link ClassCastException}. */ public final static EquivalenceRelation STRINGS_IGNORE_CASE = new EquivalenceRelation() { public boolean areEqual(Object o1, Object o2) { return o1 == null ? o2 == null : ((Ssortingng)o1).equalsIgnoreCase((Ssortingng)o2); } public int hashCode(Object o) { return o == null ? -12345 : ((Ssortingng)o).toUpperCase().hashCode(); } public Ssortingng toSsortingng() { return ""; } }; /** * Compares {@link CharSequence} implementations by content. * Other object give a {@link ClassCastException}. */ public final static EquivalenceRelation CHAR_SEQUENCE_CONTENT = new EquivalenceRelation() { public boolean areEqual(Object o1, Object o2) { CharSequence seq1 = (CharSequence)o1; CharSequence seq2 = (CharSequence)o2; if (seq1 == null ^ seq2 == null) // nur eins von beiden null return false; if (seq1 == seq2) // umfasst auch den Fall null == null return true; int size = seq1.length(); if (seq2.length() != size) return false; for (int i = 0; i < size; i++) { if (seq1.charAt(i) != seq2.charAt(i)) return false; } return true; } /** * Entrspricht String.hashCode */ public int hashCode(Object o) { CharSequence sequence = (CharSequence)o; if (sequence == null) return 0; int hash = 0; int size = sequence.length(); for (int i = 0; i < size; i++) { hash = hash * 31 + sequence.charAt(i); } return hash; } }; 

Utiliser un TreeSet aiderait-il ici? Un TreeSet exécute en réalité un comportement de commande et de définition de base à l’aide de compare / compareTo et vous permet de définir un comparateur personnalisé à utiliser dans l’ un des constructeurs .

Juste eu ce problème et mis au point une solution simple. Je ne sais pas à quel point cela demande beaucoup de mémoire. Je suis sûr que les gens peuvent l’affiner.

Lorsque le Comparator renvoie 0, les éléments correspondent.

 public static  Set filterSet(Set set, Comparator comparator){ Set output = new HashSet(); for(E eIn : set){ boolean add = true; for(E eOut : output){ if(comparator.compare(eIn, eOut) == 0){ add = false; break; } } if(add) output.add(eIn); } return output; } 

Mon cas d’utilisation était que je devais filtrer les URL en double, comme dans les URL qui pointent vers le même document. L’object URL a une méthode samePage() qui renvoie true si tout sauf le fragment est identique.

 filtered = Misc.filterSet(filtered, (a, b) -> a.sameFile(b) ? 0 : 1); 

Vous ne réussirez pas à faire votre concaténation de déduplication avec un comparateur. Vous voulez probablement faire quelque chose comme ça:

 List list = new ArrayList(); list.addAll( a ); list.addAll( b ); Collections.sort( list, new MyCustomComparator() ); 

Le problème est que Comparator a besoin de comparer non seulement pour les égaux / non-égaux, mais aussi pour l’ordre relatif. Étant donné que les objects x et y ne sont pas égaux, vous devez répondre si l’un est plus grand que l’autre. Vous ne pourrez pas faire cela, car vous n’essayez pas de comparer les objects. Si vous ne donnez pas de réponse cohérente, vous enverrez l’algorithme de sorting dans une boucle infinie.

J’ai une solution pour vous. Java a une classe appelée LinkedHashSet, dont la vertu est qu’il ne permet pas d’insérer des doublons, mais maintient l’ordre d’insertion. Plutôt que d’implémenter un comparateur, implémentez une classe wrapper pour contenir l’object réel et implémenter hashCode / equals.