18

Being able to write your own equals/hashCode for a custom class seems appealing - see Non-primitive Types in Map Keys and Sets. But with no hashCode method exposed in Apex string or decimal or any of the other primitive types, building a correct and efficient hashCode for a custom class that has a few fields of different types looks way harder than it should be.

Suggestions?

6
  • 1
    Yes most interesting, wondering why the only sample is using numbers as an example? ;-) Looks like there is a bug in this area at large anyway, success.salesforce.com/issues_view?id=a1p30000000SV0XAAW Commented Nov 19, 2012 at 23:17
  • Good gawd, that bug makes the entire feature unusable if it's really that simple a repro. Even if it wasn't there, Keith is right, not exposing hashCode in the primitives makes it more painful to roll your own. Until that happens, best bet is to create a set of primitive hash utility methods that copy the logic from Java or .NET. Still the point is moot until they fix maps and sets.
    – jkraybill
    Commented Nov 20, 2012 at 1:11
  • On na14 these pass:
    – Keith C
    Commented Nov 20, 2012 at 9:33
  • I saw the bug report but couldn't reproduce. What I see is that these pass on na14 but not on na3 system.assertEquals(1, numberSet.size()) system.assertEquals(1, numberMap.size()) and this fails on both system.assertEquals(t1, t2). A new low has been reached here.
    – Keith C
    Commented Nov 20, 2012 at 9:43
  • Salesforce broke your link so hard. Here is a newer version.
    – Adrian Larson
    Commented Jul 27, 2015 at 15:58

2 Answers 2

19

Summer'13 Update:

According to the Summer'13 release notes String now provides a hashCode method! Here is the sample code included in the release notes here.

public class MyCustomClass {
    String x,y;
    public MyCustomClass(String a, String b) {
        x=a;
        y=b;
    }
    public Integer hashCode() {
        return (31 * x.hashCode()) ^ y.hashCode();
    }
    public Boolean equals(Object obj) {
        if (obj instanceof MyCustomClass) {
            MyCustomClass p = (MyCustomClass)obj;
            return (x.equals(p.x)) && (y.equals(p.y));
        }
        return false;
    }
}

Original Answer:

Converted comment to an answer after a bit of digging around.

Initial thoughts...

Yes most interesting, I did wonder why the only sample was using numbers. It also looks like there is a bug in this area at large anyway, http://success.salesforce.com/issues_view?id=a1p30000000SV0XAAW.

Current conclusion...

I had a look at the Java implementations and a few other general postings on the net. My conclusion is that given the statement governor, at least for strings, it is going to quite expensive to implement a String.hashCode. We really need a native implementation of this to avoid hitting the statement governor very quickly with large maps.

Some interesting links

9
  • Could an MD5 hash generated using the Crypto class be used as a substitute? Commented Nov 20, 2012 at 10:06
  • Have just been trawling the Apex docs and came across that, the trick is going to be turning this into an Integer to return from the hashCode method. Commented Nov 20, 2012 at 10:14
  • It shouldnt matter right, if the string (blobs) are 'equal', the resulting hashes should be the same ? Commented Nov 20, 2012 at 11:06
  • I'm just not sure how both equals and hashCode could be implemented this way though? (putting aside its a big hammer for a large nut of course) How would the hashCode method be implemented? Commented Nov 20, 2012 at 11:15
  • 3
    Posted Expose hashCode on all Apex primitives so hashCode viable for custom classes in the IdeaExchange to at least publicise that particular problem a bit more.
    – Keith C
    Commented Nov 21, 2012 at 18:28
2

I realize Summer '13 is just around the corner, but if you are still looking for a way to create a hashcode out of strings (or any other object) without hitting governor limits, here is a method I pieced together using the solution found here

private static final Long prime = 524287L;
public static Integer getHashCode(Object obj) {
    String objHex = null;
    if(obj instanceof Id) {
        objHex = EncodingUtil.convertToHex(Blob.valueOf((String)obj));
    } else if(obj != null) {
        Blob objJSONBlob = Blob.valueOf(JSON.serialize(obj));
        Blob objHMAC = Crypto.generateMac('hmacSHA1', objJSONBlob, Blob.valueOf('a key that does not matter'));
        objHex = EncodingUtil.convertToHex(objHMAC);
    }
    if(objHex != null) {
        Long hash = 0L;
        for(String sChar : objHex.split('')){
            if(String.isEmpty(sChar)) {
                continue;
            }
            hash = ((hash ^ hexToInteger.get(sChar)) * prime);
        }
        return (Integer)hash;
    } else {
        return 0;
    }
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .