I am having 2 instances of a wrapper class holding the same values. Then I add both instances to a Set of that wrapper class and both instances are present in the set.

Say this is my code:

public class PricebookData {
    public String name;
}

Set<PricebookData> priceBooksData = new Set<PricebookData>();
PricebookData pbData = new PricebookData();
pbData.name = 'Test';
priceBooksData.add(pbData);

PricebookData pbData2 = new PricebookData();
pbData2.name = 'Test';
priceBooksData.add(pbData2);

Now the priceBooksData contains both instances of pbData and pbData2 even though they have only 1 field name and that field holds the same value.

Since these are not sObjects and don't hold Ids why are they not considered identical? Based on what? When I system.debug the value of pbData and pbData2 what gets printed on the screen is identical.

up vote 1 down vote accepted

Making a small addition to your example will illustrate what's going on (or at least part of what's going on)

public class PricebookData {
    public String name;
}

Set<PricebookData> priceBooksData = new Set<PricebookData>();
PricebookData pbData = new PricebookData();
pbData.name = 'Test';
priceBooksData.add(pbData);

PricebookData pbData2 = new PricebookData();
pbData2.name = 'Test';
priceBooksData.add(pbData2);

// We will see 2 individual entries in the set
system.debug(priceBooksData);

// The hashcodes will be different
system.debug(pbData.hashCode());
system.debug(pbData2.hashCode());

// And the equals method will return false
system.debug(pbData.equals(pbData2));

They're not identical based on the results of the equals() and hashcode() methods.

These two methods are used to both store and fetch data from maps and sets as mentioned in Using Custom Types in Map Keys and Sets

The base Object class, from which every other SObject and Apex class inherit, contains a default implementation of these two methods. It's hard to say for sure what happens in this default implementation since there isn't much/any documentation on the Object class in Apex, but given that Apex compiles to Java bytecode, I wouldn't think it a stretch to assume that we end up using the default Java implementation of hashcode().

Java's default hashcode() takes into account the memory address that your object is stored in. Two different instances of the same class would be stored at different memory addresses, so their hashcode is different.

In the end, the fix here is to listen to the advice given in "Using Custom Types in Map Keys and Sets"

When using a custom type (your Apex class) for the map key or set elements, provide equals and hashCode methods in your class. Apex uses these two methods to determine equality and uniqueness of keys for your objects.

  • Great explanation, thank you! I assumed that there's something going on with the memory addresses but was completely oblivious about the hascode and equals methods. – Arthlete Aug 10 at 19:29

For any User defined type, you need to implement the equals and hashCode methods. Refer to the below excerpt from the documentation.

Uniqueness of set elements of user-defined types is determined by the equals and hashCode methods, which you provide in your classes. Uniqueness of all other non-primitive types is determined by comparing the objects’ fields.

You will need to add these methods in your class, which should then look like something as below. Just make sure (more details here):

If two objects are equal, based on the equals method, hashCode must return the same value.

public class PricebookData {
    public String name;
    public Boolean equals(Object obj) {
        if (obj instanceOf PricebookData) {
            return this.name == ((PricebookData) obj).name;
        }
        return false;
    }
    public Integer hashCode() {
       return (your logic of calculating hashcode);
    }
}
  • Very interesting, thank you. I will give it a try right now! – Arthlete Aug 10 at 19:22

Referring Using Custom Types in Map Keys and Sets, I could able to replicate this use case as follows:

Create equals() and hashcode() which returns Integer for comparison.

public class PricebookData {
    public String name;

    public Boolean equals(Object obj) {
        if (obj instanceof PricebookData) {
            PricebookData p = (PricebookData)obj;
            return (name==p.name) ;
        }
        return false;
    }

    public Integer hashCode() {
        Integer[] chars = name.getChars();

        Integer returnValueInt = 0;
        for(integer i=0; i<chars.size();i++)
        {
            returnValueInt = returnValueInt + chars[i];
        }
        return (300 * returnValueInt);
    }
}

Testing

PricebookData p1 = new PricebookData();
p1.name = 'Test';
PricebookData p2 = new PricebookData();
p2.name = 'Test1';
// Duplicate key
PricebookData p3 = new PricebookData();
p3.name = 'Test';
// Create a set
Set<PricebookData> s1 = new Set<PricebookData>();
s1.add(p1);
s1.add(p2);
s1.add(p3);

// Verify that we have only two elements
// since the p3 is equal to p1.
System.debug('final size=' + s1.size()); //this is 2.
  • 1
    Thank you, I really like your hashCode example! – Arthlete Aug 10 at 19:43

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.