8

I have this below code where I create a map with sobject as key and integer as value. I added 2 account sobject keys. Size is 2, and I later changed one of its names to the other one so that now they are duplicated.

Ideally map since it's a reference to sobject, changes should be reflected and map size should shrink to one. In my code, it does only if i do a system.debug of map before i calculate size/assert. It's the same behavior for set as well. Why is that so?

map<account,integer> mymap = new map<account,integer>();
account a = new account(name='apple');
mymap.put(a,1);
system.assertequals(mymap.size(),1);
mymap.put(new account(name='orange'),1);
system.assertequals(mymap.size(),2);
a.name='orange';
system.debug(mymap);//if i remove this my assertion will fail and size will be displayed as 2.if i add this statement before ,size is 1 and assertion succeeds
system.debug(mymap.size());
system.assertequals(mymap.size(),1);

4 Answers 4

12

I've come across a similar issue before in Using an sObject as a Map key.

From Map Considerations:

Uniqueness of map keys of user-defined types is determined by the equals and hashCode methods, which you provide in your classes. Uniqueness of keys of all other non-primitive types, such as sObject keys, is determined by comparing the objects’ field values.

And sObject Map Considerations

Be cautious when using sObjects as map keys. Key matching for sObjects is based on the comparison of all sObject field values. If one or more field values change after adding an sObject to the map, attempting to retrieve this sObject from the map returns null. This is because the modified sObject isn’t found in the map due to different field values. This can occur if you explicitly change a field on the sObject, or if the sObject fields are implicitly changed by the system; for example, after inserting an sObject, the sObject variable has the ID field autofilled. Attempting to fetch this Object from a map to which it was added before the insert operation won’t yield the map entry, as shown in this example.

Something interesting I found. As you say, the assertions pass when I run your sample code as anonymous apex with the Apex Code logging level set to DEBUG. However, with exactly the same code, if you set the Apex Code logging level to FINEST it will fail the assertion!

3
  • yes you are correct..it fails assertions when log level is changed ..Interesting :).and thanks
    – sfdc99999
    Jun 27, 2014 at 9:20
  • 1
    Nitpick: your first documentation quote is untrue for apex classes that have defined hashCode and equals methods, but is certainly the case for SObjects. Jun 28, 2014 at 20:12
  • @ca_peterson Good catch see the docs had been updated to include this since the quote was originally taken. I've updated the details here to be more accurate. Jun 29, 2014 at 9:15
4

This reminded me of an exact section in Adavanced Apex Programming by Dan Appleman. The section blatantly was called Avoid Using Objects as Keys.

Apex uses a hash of the field values as the internal value to use when searcing for the object in the map or set. Changing a field on an object changes this hash value, causing the same object to appear as two distinct objects when used as keys.

Given that one of the main purposes of maps and sets when used with objects is to hold them while they are being modified, using objects as keys or in sets is a sure way to create subtle and hard to find bugs.

2
  • 1
    This, and a number of other statements I've heard from Dan Appleman makes me distrust his books. There are perfectly valid reasons for using objects as keys. Any advanced programmer would jump right in and use them wisely, while a novice would jump right in and cause subtle bugs mentioned by Dan. Please tell me this quote isn't from his "Advanced Apex Code" book.
    – sfdcfox
    Jul 1, 2014 at 17:18
  • There are objects and sobjects. Sobjects are closely coupled to the persistence layer and you can’t modify their key behaviour. So using them as key is, mildly said, problematic. Most likely you would use the sobject.id as a map key, eventually with 2 connected maps or a custom class holding the sobject and the other values. For your own objects you have full control over the key behaviour, so that might work. Could you share a use case where (s)objects as keys are the solution?
    – stwissel
    May 22, 2018 at 14:49
1

Here is a Known issue filed against the issue: https://success.salesforce.com/issues_view?id=a1p30000000T4hVAAS

1
  • This isn't really a bug, though. It's by design. Keys are meant to be immutable, and if you violate that design, you get what you ask for. If they "fix" it to use memory addresses, it will break code in many places, including my own.
    – sfdcfox
    Jul 1, 2014 at 17:58
0

There are a lot of odd things that go on with the collections in order to support the various legacy interaction with the rest of platform and this is one of them. Salesforce R&D has a bug filed with them with no clear ETA.

You must log in to answer this question.

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