[ACCEPTED]-Is it possible in java make something like Comparator but for implementing custom equals() and hashCode()-hashcode
Yes it is possible to do such a thing. (And 28 people have done it.) But it won't allow 27 you to put your objects into a HashMap
, HashSet
, etc. That 26 is because the standard collection classes 25 expect the key objects themselves to provide 24 the equals
and hashCode
methods. (That is the way they 23 are designed to work ...)
Alternatives:
Implement 22 a wrapper class that holds an instance of 21 the real class, and provides its own implementation 20 of
equals
andhashCode
.Implement your own hashtable-based 19 classes which can use a "hashable" object 18 to provide equals and hashcode functionality.
Bite 17 the bullet and implement
equals
andhashCode
overrides 16 on the relevant classes.
In fact, the 3rd 15 option is probably the best, because your 14 codebase most likely needs to to be using a consistent 13 notion of what it means for these objects 12 to be equal. There are other things that 11 suggest that your code needs an overhaul. For 10 instance, the fact that it is currently 9 using an array of objects instead of a Set 8 implementation to represent what is apparently 7 supposed to be a set.
On the other hand, maybe 6 there was/is some real (or imagined) performance 5 reason for the current implementation; e.g. reduction 4 of memory usage. In that case, you should 3 probably write a bunch of helper methods 2 for doing operations like concatenating 1 2 sets represented as arrays.
90% of the time when a user wants an equivalence 5 relation there is already a more straightforward 4 solution. You want to de-duplicate a bunch 3 of things based on ids only? Can you just 2 put them all into a Map with the ids as 1 keys, then get the values()
collection of that?
HashingStrategy is the concept you're looking for. It's 16 a strategy interface that allows you to 15 define custom implementations of equals 14 and hashcode.
public interface HashingStrategy<E>
{
int computeHashCode(E object);
boolean equals(E object1, E object2);
}
As others have pointed out, you 13 can't use a HashingStrategy
with the built in HashSet
or HashMap
. Eclipse Collections includes 12 a set called UnifiedSetWithHashingStrategy
and a map called UnifiedMapWithHashingStrategy
.
Let's look 11 at an example. Here's a simple Data
class we 10 can use in a UnifiedSetWithHashingStrategy
.
public class Data
{
private final int id;
public Data(int id)
{
this.id = id;
}
public int getId()
{
return id;
}
// No equals or hashcode
}
Here's how you might set up 9 a UnifiedSetWithHashingStrategy
and use it.
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)));
Why not just use a Map
? UnifiedSetWithHashingStrategy
uses 8 half the memory of a UnifiedMap
, and one quarter the 7 memory of a HashMap
. And sometimes you don't have 6 a convenient key and have to create a synthetic 5 one, like a tuple. That can waste more memory.
How 4 do we perform lookups? Remember that Sets 3 have contains()
, but not get()
. UnifiedSetWithHashingStrategy
implements Pool
in addition 2 to MutableSet
, so it also implements a form of get()
.
Note: I 1 am a committer for Eclipse Collections.
Of course you can create some external object 10 providing an equality comparison and a HashCode. But 9 the build-in collections of Java do not 8 use such an object for their comparisons/lookup.
I 7 once did create an interface like this in 6 my package-collection (just freshly translated 5 to english):
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);
}
Than I had a group of interfaces 4 CustomCollection
, CustomSet
, CustomList
, CustomMap
, etc. defined like the interfaces 3 in java.util
, but using such an equivalence relation 2 for all the methods instead of the build-in 1 relation given by Object.equals
. I had some default implementations, too:
/**
* 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;
}
};
Use Guava Equivalence:
Equivalence<T> equivalence = new Equivalence<T>() {
@Override
protected boolean doEquivalent(T a, T b) {
return CustomComparator.equals(a, b);
}
@Override
protected int doHash(T item) {
return CustomHashCodeGenerator.hashCode(item);
}
};
List<T> items = getItems();
Set<Equivalence.Wrapper<T>> setWithWrappedObjects = items.stream()
.map(item -> equivalence.wrap(item))
.collect(Collectors.toSet());
0
Would using a TreeSet help here? A TreeSet actually 3 performs ordering and Set based behavior 2 using compare/compareTo and allows you to 1 define a custom Comparator for use in one of the constructors.
Just had this problem and worked up a simple 8 solution. Not sure how memory-intensive 7 it is; I'm sure people can refine it down 6 the line.
When the Comparator
returns 0, the elements 5 match.
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;
}
My use case was that I needed to filter 4 out duplicate URLs, as in URLs that point 3 to the same document. The URL object has 2 a samePage()
method that will return true if everything 1 except the fragment are the same.
filtered = Misc.filterSet(filtered, (a, b) -> a.sameFile(b) ? 0 : 1);
You will not succeed doing your de-duplicating 18 concatenation with a Comparator. Presumably 17 you are looking to do something like this:
List<Object> list = new ArrayList<Object>();
list.addAll( a );
list.addAll( b );
Collections.sort( list, new MyCustomComparator() );
The 16 problem is that Comparator needs to compare 15 not just for equals/not-equals, but also 14 for relative order. Given objects x and 13 y that are not equal, you have to answer 12 if one is greater than the other. You will 11 not be able to do that, since you aren't 10 actually trying to compare the objects. If 9 you don't give a consistent answer, you 8 will send the sorting algorithm into an 7 infinite loop.
I do have a solution for you. Java 6 has a class called LinkedHashSet, whose 5 virtue is that it doesn't allow duplicates 4 to be inserted, but maintains insertion 3 order. Rather than implementing a comparator, implement 2 a wrapper class to hold the actual object 1 and implement hashCode/equals.
More Related questions
We use cookies to improve the performance of the site. By staying on our site, you agree to the terms of use of cookies.