1

How do I implement hashCode() for a custom class with more than 2 variables? From the docs, the example only has two values used:

return (31 * x) ^ y;

I'm working with user data such as first name, last name, dob, and address. I risk not creating a unique hash if I can only pick two of these.

2
  • Is there an issue with using the hashCode() method provided to every class by default (by virtue of implicitly inheriting from the Object class)?
    – Derek F
    Commented May 3, 2021 at 17:47
  • if you look at my docs link, it says you need to implement your own hashCode() method when creating a custom type.
    – Tyler Zika
    Commented May 3, 2021 at 17:51

2 Answers 2

3

The hashCode() value isn't the only factor involved, it also checks equals(), but be aware of choosing a bad hash. It should probably be sufficient to say:

return (
  firstName+' '+
  lastName+' '+
  dob.format()+' '+ 
  address
).normaliseSpace().toLowerCase().hashCode();

Or something. Having any reasonable hash code should work just fine, since equals() will resolve any discrepancies.

The reason why they suggest something like (31 * x) ^ y or so is that it kind of shuffles the bits around in a decently unique way, but does not prevent all collisions. By adding together our data as such and then using String's hashCode, we present a similar solution.

3
  • I would not recommend this approach. It is highly inefficient and will not perform well if you are doing heavy Map processing (or Set usage etc.) with the object to which this hash code function (and equals) relate.
    – Phil W
    Commented May 3, 2021 at 19:00
  • @PhilW This is more meant as a hypothetical, although to be fair, I've used code of about this quality and it's worked just fine. YMMV depending on the data, though. As an alternative to calculating on the fly, the hash code could also be determined during construction/initialization, thus reducing the hashCode call to a simple return value.
    – sfdcfox
    Commented May 3, 2021 at 19:08
  • That's an option. We tend to combine the hash codes of the various attributes themselves. An alternative is to add the various attributes into a List<Object> in a specific order and take the hash from that. This means you get the performance of behind-the-API processing without the expense of String building (short arrays are far more performant).
    – Phil W
    Commented May 3, 2021 at 19:19
3

To comment on and extend the answer from @sfdcfox, I recommend against conversion to a string in order to hash, especially if your object will be used in a Set or Map in code that is executed often, because string concatenation is slow (and generates significant amounts of garbage to be collected). More than that, since fox's suggestion is to effectively ignore whitespace and case, similar values varying only by whitespace or case will receive the same hash - this may or may not be appropriate for your use case.

My recommendation is to leverage List<Object> since this already supports hashCode based on its content. As long as each of the object's attributes are of types that support hashCode (which could be your very own implementation on an Apex class using this very same approach) and your attributes are a, b, c and d, then your hashCode function can look something like:

public Integer hashCode() {
    return new List<Object>{ a, b, c, d }.hashCode();
}

Equality should use the objects' types (classes) and attribute values individually, whilst allowing for null where necessary. However, you can again leverage the power of List<Object> to apply much of this logic (assuming your class is called MyClass):

public Boolean equals(Object other) {
    if (other instanceof MyClass) {
        MyClass that = (MyClass) other;

        return new List<Object>{ a, b, c, d } == new List<Object>{ that.a, that.b, that.c, that.d };
    }

    return false;
}

The hash use of List<Object> is far more efficient than the equivalent string conversion/concatenation one and will always win hands down in terms of performance (we have done analysis on this on earlier versions of the Salesforce platform). The use of the list in equals is actually less efficient than individual property checks, but makes the code far shorter.

At the risk of a little more code complexity, you could implement your attributes as properties and ensure that the setters update a private List<Object> property at the appropriate index in order to effectively cache the list through the lifecycle of your MyClass instances, though I would not recommend this approach unless it is essential for performance.

You must log in to answer this question.

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