function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
anschoeweanschoewe 

Sets do not enforce uniqueness of sobjects according to documenation

Hello,

 

I'm trying to avoid duplicate sobjects in my list of sobjects that I submit for update/delete/insert.  I realized that Sets are great for this.  The documentation states the following concerning uniqueness of sObjects in Sets:

 

http://www.salesforce.com/us/developer/docs/apexcode/index.htm

 

"Uniqueness of sObjects is determined by IDs, if provided. If not, uniqueness is determined by comparing fields. For example, if you try to add two accounts with the same name to a set, only one is added"

 

However, in my experience, this is not the case.  In the following example, I have provided the ID of the opportunities, but after changing one field, both opportunities are still added to the Set.  This is not the expected behavior because the ID of the opportunities are supplied and identical.

 

 

Opportunity opp1 = [Select Id from Opportunity Where  Id = '006Q00000054J7u'];
Set<Opportunity> opps = new Set<Opportunity>(); 
opps.add(opp1); 
opp1.Name = 'Something new';
opps.add(opp1);
System.debug('SIZE: ' + opps.size()); //prints 2, expect 1

 

 

 

What am I doing wrong?  Is this an API version issue?  I believe I'm using api version 19.0.

 

I will need to rewrite a lot of code if the Set uniqueness does not work as advertised.

 

Thanks for any help you might provide,

 

Andrew

Best Answer chosen by Admin (Salesforce Developers) 
rungerrunger

There is a bug in the documentation.  Hash codes are computed based on the field values, and do not treat the ID in any special manner.  Thanks for finding that, I'll make sure the docs get fixed.

 

With respect to what's happening in the code, you can see the same behavior in Java.  What's happening is that you're mutating an object that already exists in the set, changing its hashcode.  So any further lookups will happen in the new hash location, but the old object is sitting at the old hash location.  Here's a java program that illustrates the same thing:

 

 

import java.util.*;

public class MutableTest {
    static class Value {
        String v;

        @Override
        public int hashCode() {
            if (v == null) return 0;
            return v.hashCode();
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof Value) {
                Value other = (Value)o;
                if (v == null) return other.v == null;
                return v.equals(other.v);
            }
            return false;
        }
    }

    public static void main(String[] args) {
        Set<Value> s = new HashSet<Value>();
        Value x = new Value();
        x.v = "hello";
        s.add(x);
        x.v = "world";
        s.add(x);
        System.out.println("size=" + s.size());
    }
}

 

 

A better way to have a data structure that guarantees uniqueness with sobjects is to use a map.  Apex makes it easy to generate such a map from a query:

 

 

Map<ID, Opportunity> m = new Map<ID, Opportunity>([select id from Opportunity]);

 

Rich

 

All Answers

JoyDJoyD

I'm not sure about your underlying question, but it seems you could just check to see if the set contains the item before adding - if so, update the existing one, if not, update it?  using the contains() method to check?

I'm sure someone else will come along and answer this better but at least that's an attempt ;-)  HTH

anschoeweanschoewe

Thanks for the quick reply.  using 'contains()' will not work because it uses the same logic as add() to determine if the element is already in the Set (I assume).  In Java, this would call hashCode(), and in Apex, I thought it compared an sObject's ID to the ID of the element you're trying to add (or a comparison of all the other fields if the ID field was not present).

 

Andrew

rungerrunger

There is a bug in the documentation.  Hash codes are computed based on the field values, and do not treat the ID in any special manner.  Thanks for finding that, I'll make sure the docs get fixed.

 

With respect to what's happening in the code, you can see the same behavior in Java.  What's happening is that you're mutating an object that already exists in the set, changing its hashcode.  So any further lookups will happen in the new hash location, but the old object is sitting at the old hash location.  Here's a java program that illustrates the same thing:

 

 

import java.util.*;

public class MutableTest {
    static class Value {
        String v;

        @Override
        public int hashCode() {
            if (v == null) return 0;
            return v.hashCode();
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof Value) {
                Value other = (Value)o;
                if (v == null) return other.v == null;
                return v.equals(other.v);
            }
            return false;
        }
    }

    public static void main(String[] args) {
        Set<Value> s = new HashSet<Value>();
        Value x = new Value();
        x.v = "hello";
        s.add(x);
        x.v = "world";
        s.add(x);
        System.out.println("size=" + s.size());
    }
}

 

 

A better way to have a data structure that guarantees uniqueness with sobjects is to use a map.  Apex makes it easy to generate such a map from a query:

 

 

Map<ID, Opportunity> m = new Map<ID, Opportunity>([select id from Opportunity]);

 

Rich

 

This was selected as the best answer
anschoeweanschoewe

Thanks for confirming that there is a bug in the documentation.  I thought I was going a little crazy.

 

And with regard to your Java example, you are also correct.  It all comes down to the equals and hashcode() implementation.  Since Apex doesn't have these concepts (at least not explicitly), I assumed the having the same ID on two objects made them 'equal.'

 

Just before you responded I started changing my code to use Maps instead of Sets.  Your response validates my new approach for managing update/delete/insert lists. It will do exactly what I had hoped for initially.

 

Thanks for your thorough response,

 

Andrew

MandyKoolMandyKool

Hi,

 

I have been tinkering around with equals() and hashCode() methods.

I have seen couple of sites for Java where the importance and usage of equals() and hashCode() method is explained in detail. But when it comes to Apex; its bit confusing.

 

I have a doubt that wheather we should provide implementation of hashCode() in Apex?

 

I have used the class in the documentation (PairNumbers); but did not provided the hashCode() implementation. Then I have gone to developer console and ran following code

 

Map<PairNumbers, String> m = new Map<PairNumbers, String>();
PairNumbers p1 = new PairNumbers(1,2);
PairNumbers p2 = new PairNumbers(3,4);
PairNumbers p3 = new PairNumbers(1,2);
m.put(p1, 'first');
m.put(p2, 'second');
m.put(p3, 'third');
PairNumbers p5 = new PairNumbers(1,2);
system.debug('This is what I put in?--' + m.get(p5));

 After executing the code; I got the output as:

This is what I put in?--third

 As per my understanding the hashCode() for p5 should be different than p3 and it should return null. I am missing something? I am not getting why we need to implement hashCode() method if only equals() method is doing the job or there my understanding is incorrect.

 

Thanks,

Mandy

Thomas DvornikThomas Dvornik

Why should p5 be different than p3? As per the apex documentation:

 

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

This has to be true weither you implement the hashCode function or not. For that matter, it is part of the java docs as well:

 

  • If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.

You shouldn't have to implement the hashCode method, although you're right that the docs don't make that clear.  From my understanding, the only time you would need to override the hashcode function is you wanted to change the hashcode function/functionality. Let's say you wanted to have two distinct values have the same hashcode, which is valid.