14

More specifically I want an interface to compare objects which can be only compared for equality [e.g. complex numbers] but don't have total order on them. It should have [Note that it only returns a boolean yes/no]

boolean Equals(T object1, T object2);

and a hashcode function. So that when I distribute them into buckets using objects say to collect "equal" objects, 2 "equal" objects don't end up in two different buckets.

int getHashCode(T object);

Does Java have one? I searched and couldn't find it.

I am trying to use this in Hadoop Map Reduce to distribute "equal" objects to same reduce job so that I can operate on all "equal" objects. I only care about whether objects are equal or not and don't need total order. But if two objects are equal they should have same hash code. Otherwise they will end up in two different reduce jobs.

Please note that I know about equals and hashcode of object. But I want an external comparator which say depends on only part of an object. So object's notion of equality differs from mine.

Fakrudeen
  • 5,778
  • 7
  • 44
  • 70
  • I have no idea about the C# analogy, but have you checked `Object` class? It has `equals()` and `hashCode()` methods. It's basically inherited (and overrideable) in every class. http://download.oracle.com/javase/6/docs/api/java/lang/Object.html Or maybe you need the `Comparator`? http://download.oracle.com/javase/6/docs/api/java/util/Comparator.html – BalusC Jan 29 '11 at 15:59
  • 5
    No - I want an external comparator - I have different notion of "equality" from the equality of the objects themselves. Say I want to compare only part of the object, but the object itslef implements equality on all fields. – Fakrudeen Jan 29 '11 at 16:02
  • standard java.util doesn't support similar functionality. – bestsss Jan 29 '11 at 16:03
  • I think the `Comparator` comes close anyway. It doesn't return `boolean`, but you could just check if it returns `0` or not. – BalusC Jan 29 '11 at 16:10
  • 1
    @Balsu - Yes - that was my first try. But comparator unfortunately doesn't have hashcode. So I don't have a clue how to distribute objects into buckets- because of comparator's notion of equality is only known to it, any distribution I do can throw equal objects into different buckets. – Fakrudeen Jan 29 '11 at 16:15
  • You could just test the object's hashcode inside `compareTo()` method. Or do I miss something crucial? Anyway, I think one who's strong in both languages (Jon Skeet?) can answer this better than I. – BalusC Jan 29 '11 at 16:18
  • @BalusC, **compareTo is NOT used in hashing algorithms**. Plus, I have already answered it quite clearly. – bestsss Jan 29 '11 at 16:22
  • @Fakrudeen, To put it simple: if you need external algorithm for hash/equals you have to implement your own hashmaps. There are some preformance issues relying on external hashing/equality, though. It's quite hard for the VM to inline similar code and that is a decent performance killer. Morealso the provided interface should be serializable and it also will 'break' some persistence structures, if the equality cannot be determined beforehand. – bestsss Jan 29 '11 at 16:25
  • Sidenote: hibernate for instance relies on similar external interfaces – bestsss Jan 29 '11 at 16:27
  • @bestsss: I now understand what the OP want. Thanks :) I have however not seen your "quite clear answer" anywhere? – BalusC Jan 29 '11 at 16:32
  • @BalusC, `standard java.util doesn't support similar functionality`, imo it's quite obvious – bestsss Jan 29 '11 at 16:34
  • @bestsss: not to me. Maybe only to ones who knows both C# and Java. It was only your boldfaced "compareTo is NOT used in hashing algorithms" comment which made me understand that OP is basically looking for an external comparator for hashCode. – BalusC Jan 29 '11 at 16:55
  • @BalusC, i added the bold part since compareTo/comparator was discussed after the 'so-obvious' remark. Yet, I guess you have a point about c#/java. I am horrid at teaching since I often expect the people know quite much and obviously non-evident stuff. – bestsss Jan 29 '11 at 16:59

4 Answers4

11

There's no built-in type that is used for this in Java. It's a "hole" in the collections design, IMO. There's the string-specific Collator class which is about as close as it gets, I'm afraid.

There's no way of customizing the built-in maps to use a specific kind of equality comparison, worse luck. It's entirely reasonable to want this functionality, and a real pain that it's not already present.

You could create your own such interface of course, and write your own map variants which use it... but having to do so sucks :(

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Yes - I ended up defining my own EqualityComparator which is an exact copy IEqualityComparer as I only need this contract between my map-reduce component and users. For someone trying to use HashSet etc. it will be very painful. – Fakrudeen Jan 30 '11 at 08:46
  • IIRC, there are some implementations, Google for "Equalator". – maaartinus Jan 31 '11 at 10:59
7

The type you want is Guava's Equivalence. However, you may be disappointed, as in Java Collection and Map are fairly rigidly specified in terms of Object.equals, and you will not find implementations of those in Guava that use an alternate equivalence. You can, however, simulate that behavior a bit using myEquivalence.wrap(myObject).

Kevin Bourrillion
  • 40,336
  • 12
  • 74
  • 87
2

I would suggest using a function based approach to generate the buckets like the MultiMaps.index() method in Google collections (now Guava) does. They use a Function<V,K> which maps objects of type V to keys of type K (in your case the buckets).

Florian
  • 1,281
  • 7
  • 17
  • 2
    Interestingly, Guava defines the [Equivalence](http://guava-libraries.googlecode.com/svn/tags/release08/javadoc/com/google/common/base/class-use/Equivalence.html) interface for this, but does not use it in any of its Map implementations. – finnw Jan 29 '11 at 18:35
  • 1
    @finnw - That's exactly what I want. But I don't want to use that library just for that interface. But it is good to know. I will use it next time, when I already have a dependency on it. – Fakrudeen Jan 30 '11 at 08:23
0

In the end I opted to write what I do in similar cases. If I need special equality/hash - like for example keeping weak references. You can wrap the key like that. Overall it's not very different from the interface approach but it creates dumb instances (like HashMap/Hashtable for bucket entries). You might need extra unwrapping for keySet() and so on...

package t1;

public abstract class KeyX<Key> implements java.io.Serializable {
    private static final long serialVersionUID = 0l;

    final Key key;
    final int hash;
    protected KeyX(Key key){
        this.key = key;
        this.hash = hashCode(key);
    }

    protected abstract int hashCode(Key key);

    //Key, Key will be way too strict and it'd required, key.getClass().isInstance(y) prior calling
    protected abstract boolean equals(Key x, Object y);

    @Override
    public final boolean equals(Object obj) {
        if (obj==this)
            return true;
        if (!(obj instanceof KeyX)){
            return false;
        }
        final KeyX<?> other = (KeyX<?>) obj;
        return this.key==other.key ||  (hash==other.hash && other.key!=null  && equals(this.key, other.key)); 

    }

    @Override
    public final int hashCode() {
        return hash;
    }

    public final Key unwrap(){
        return key;
    }
}
bestsss
  • 11,796
  • 3
  • 53
  • 63