method example java collections equals hashcode

example - override equals java



¿Es posible en Java hacer algo como Comparator pero para implementar custom equals() y hashCode() (7)

¿ TreeSet una ayuda de TreeSet aquí? Un TreeSet realmente realiza el orden y el comportamiento basado en Set usando compare / compareTo y le permite definir un Comparador personalizado para usar en uno de los constructores .

Tengo una matriz de objetos y quiero concatenarla con otra matriz de objetos, excepto que tienen los mismos ID. Los objetos se usan en muchos lugares del sistema y no tienen código hash o se implementan iguales. Así que no quiero implementar hashCode() y equals() , porque me da miedo romper algo en algún lugar del sistema donde se usan esos objetos y no sé nada de eso.

Quiero poner todos esos objetos en un conjunto, pero de alguna manera hago que los objetos usen hashCode() y equals() . Algo así como el Comparator personalizado, pero para iguales.


El 90% del tiempo cuando un usuario quiere una relación de equivalencia ya existe una solución más directa. ¿Desea quitar la duplicación de varias cosas solo en los identificadores? ¿Puedes ponerlos todos en un mapa con los identificadores como claves, y luego obtener la colección values() de eso?


No logrará realizar su concatenación de deduplicación con un Comparador. Presumiblemente, usted está buscando hacer algo como esto:

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

El problema es que Comparator necesita comparar no solo para iguales / no iguales, sino también para el orden relativo. Dado los objetos x e y que no son iguales, debe responder si uno es mayor que el otro. No podrás hacer eso, ya que no estás tratando de comparar los objetos. Si no da una respuesta coherente, enviará el algoritmo de clasificación a un bucle infinito.

Tengo una solución para ti. Java tiene una clase llamada LinkedHashSet, cuya virtud es que no permite la inserción de duplicados, pero mantiene el orden de inserción. En lugar de implementar un comparador, implemente una clase contenedora para contener el objeto real e implemente hashCode / equals.


Por supuesto, puede crear algún objeto externo que proporcione una comparación de igualdad y un HashCode. Pero las colecciones integradas de Java no usan dicho objeto para sus comparaciones / búsquedas.

Una vez creé una interfaz como esta en mi paquete-colección (recién traducido al inglés):

public interface HashableEquivalenceRelation { /** * Returns true if two objects are considered equal. * * This should form an equivalence relation, meaning it * should fulfill these properties: * <ul> * <li>Reflexivity: {@code areEqual(o, o)} * should always return true.</li> * <li>Symmetry: {@code areEqual(o1,o2) == areEqual(o2,o1)} * for all objects o1 and o2</li> * <li>Transitivity: If {@code areEqual(o1, o2)} and {@code areEqual(o2,o3)}, * then {@code areEqual(o1,o3}} should hold too.</li> * </ul> * Additionally, the relation should be temporary consistent, i.e. 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 * <em>change significantly</em> 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, i.e. 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); }

CustomCollection tuve un grupo de interfaces CustomCollection , CustomSet , CustomList , CustomMap , etc. definidas como las interfaces en java.util , pero usando esa relación de equivalencia para todos los métodos en lugar de la relación de construcción dada por Object.equals . También tuve algunas implementaciones predeterminadas:

/** * 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 String toString() { return "<DEFAULT>"; } }; /** * 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 String toString() { return "<IDENTITY>"; } }; /** * 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 String toString() { return "<ALL>"; } }; /** * 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 String toString() { return "<NULL_OR_NOT_NULL>"; } }; /** * 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 String toString() { return "<SAME_CLASS>"; } }; /** * Compares strings 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 : ((String)o1).equalsIgnoreCase((String)o2); } public int hashCode(Object o) { return o == null ? -12345 : ((String)o).toUpperCase().hashCode(); } public String toString() { return "<STRINGS_IGNORE_CASE>"; } }; /** * 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; } };


Sí, es posible hacer tal cosa. Pero no le permitirá colocar sus objetos en un HashMap, HashSet, etc. Esto se debe a que las clases de colección estándar esperan que los objetos clave proporcionen los métodos hashCode y hashCode . (Esa es la forma en que están diseñados para funcionar ...)

Alternativas:

  1. Implemente una clase contenedora que contenga una instancia de la clase real y proporcione su propia implementación de equals y hashCode .

  2. Implemente sus propias clases basadas en hashtable que pueden usar un objeto "hashable" para proporcionar la funcionalidad de igual y código hash.

  3. Muerde la bala e implementa equals y hashCode anula las clases relevantes.

De hecho, la tercera opción es probablemente la mejor, ya que es muy probable que la base de código necesite utilizar una noción coherente de lo que significa que estos objetos sean iguales. Hay otras cosas que sugieren que su código necesita una revisión. Por ejemplo, el hecho de que actualmente utiliza una matriz de objetos en lugar de una implementación de Conjunto para representar lo que aparentemente se supone que es un conjunto.

Por otro lado, tal vez hubo / hay alguna razón de rendimiento real (o imaginaria) para la implementación actual; por ejemplo, reducción del uso de la memoria. En ese caso, probablemente debas escribir un montón de métodos de ayuda para realizar operaciones como concatenar 2 conjuntos representados como matrices.


Solo tuve este problema e ideé una solución simple. No estoy seguro de cuán intensivo de memoria es; Estoy seguro de que la gente puede refinarlo más adelante.

Cuando el Comparator devuelve 0, los elementos coinciden.

public static <E> Set<E> filterSet(Set<E> set, Comparator<E> comparator){ Set<E> output = new HashSet<E>(); 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; }

Mi caso de uso fue que necesitaba filtrar URL duplicadas, como en las URL que apuntan al mismo documento. El objeto URL tiene un método samePage() que devolverá true si todo, excepto el fragmento, es el mismo.

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


HashingStrategy es el concepto que estás buscando. Es una interfaz de estrategia que le permite definir implementaciones personalizadas de iguales y hashcode.

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

Como han señalado otros, no puede usar una HashingStrategy con HashSet o HashMap HashSet . Eclipse Collections incluye un conjunto llamado UnifiedSetWithHashingStrategy y un mapa llamado UnifiedMapWithHashingStrategy .

Veamos un ejemplo. Aquí hay una clase de Data simple que podemos usar en UnifiedSetWithHashingStrategy .

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

A continuación, le mostramos cómo puede configurar un UnifiedSetWithHashingStrategy y usarlo.

java.util.Set<Data> 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)));

¿Por qué no usar un Map ? UnifiedSetWithHashingStrategy utiliza la mitad de la memoria de UnifiedMap y la cuarta parte de la memoria de un HashMap . Y a veces no tienes una clave conveniente y tienes que crear una sintética, como una tupla. Eso puede desperdiciar más memoria.

¿Cómo realizamos búsquedas? Recuerde que Sets tiene contains() , pero no get() . UnifiedSetWithHashingStrategy implementa Pool además de MutableSet , por lo que también implementa una forma de get() .

Nota: soy un committer para las colecciones de Eclipse.