Considerations Using sObjects in Sets and Map Keys in Apex

January 3, 2013 Appirio

by Ray Dehler (@rdehler)

With the Winter ‘13 release of Salesforce, you can now use non-primitive types in Map keys and Sets. Non-primitive types can include Apex classes as well as SObjects, both standard and custom.  

For Apex classes that are expected to be used in Sets or keys for Maps, you should create equals and hashCode methods. You cannot do the same for SObjects, however; you are restricted to using a field-by-field comparison.  In other words, in the following snippet, the second Account key will overwrite the first Account in the Map.

Account a1 = new Account(Name = ‘Ray Test’);
Account a2 = new Account(Name = ‘Ray Test’);
Map theMap = new Map();
theMap.put(a1, ‘first’);
theMap.put(a2, ‘second’);

The debug output will be:
{Account:{Name=Ray Test}=second}

Even though these are two distinct Accounts (Name is not a unique field), our Map only contains one instance.  

Furthermore, when using a SObject as a key for the Map, it is possible to edit the key for this Map.  While allowed, this has an unforeseen consequence. Consider the following code snippet:

Account a1 = new Account(Name = ‘Ray Test’);
Map theMap = new Map();
theMap.put(a1, ‘Value’);
insert a1;

The debug output will be:
{Account:{Name=Ray Test}=Value}

{Account:{Name=Ray Test, Id=001F000000pIJiDIAW}=null}

Note that the value of this Map entry was truncated.  A similar result is seen when, instead of inserting, you simply change any value of the a1 object.  

Note that this is NOT the case with non-SObject non-primitive data types. Here’s a sample with an Apex class as a Map key where the issue does not occur:
public class ObjectKey {
   public String name;
   public String anotherValue;

ObjectKey o1 = new ObjectKey(); = ‘Ray Test’;
Map theMap = new Map();
theMap.put(o1, ‘Value’);
o1.anotherValue = ‘Another Value’;

In this case, we see the following for debug logs:
{ObjectKey:[anotherValue=null, name=Ray Test]=Value}

{ObjectKey:[anotherValue=Another Value, name=Ray Test]=Value}

When planning to use an SObject as the key for a Map, be aware that it can sometimes behavein an unexpected fashion.  If you are expecting to encounter a scenario where the SObject can be mutated (even indirectly, as done by an insert) once set as a key for this Map, you will lose the reference to the value for this Map for the SObject key that was mutated.

Previous Article
Development Lifecycle with and Git
Development Lifecycle with and Git

by Geoff Escandon gescandon@appirio.comDevelopment in the Cloud is rapidly becoming an bazaar of languages ...

Next Article
Comparing Ruby HTTP Clients

Hiroshi Nakamura (@nahi) As a Technical Architect at Appirio Japan, and also as an OSS developer and enthus...