Print this page

Unable to find a match in map of sObjects

Knowledge Article Number 000176499
Description I am using a map of sObjects to Integers and have noticed that when I try to retrieve the value associated to the account I stored in the map, I get null. Is this a bug?

Account a = new Account(name='a1');

Map<Account, Integer> m = new Map<Account, Integer>{ a => 1};
a.Industry = 'Consulting';
system.assertNotEquals(m.get(a), null); // This will throw an error as m.get(a) is null
Resolution As in user-defined types uniqueness of map keys for sObjects is determined by equals() and hashCode() methods, that are based on the values in the sObject. So while the account stored in the map and the one referenced by the variable 'a' are the same objects, the hash that was computed when the map was populated does not match the hash of 'a' after the object is modified. In other words, if you have mutable objects used as map keys, then changing them once they are in the map can cause false misses.

As a result, great care must be exercised if mutable objects are used as map keys as the behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map.

Notice this is also the reason why there are false misses when a map of sObjects populated in a before insert trigger is used in an after insert trigger, which is exemplified by the following code:

public without sharing class AccountTriggerHandler{
    public static Map<SObject,Integer> accMap;
 
    public void onBefore() {
        accMap = new Map<SObject,Integer>();
        Integer counter = 0;
        for(SObject obj: Trigger.new) {
            Account a = (Account)obj;
            accMap.put(a, counter);
            counter++;
        }
        System.debug('accMap: ' + accMap);
    }
 
    public void onAfter() {
        System.debug('accMap: ' + accMap);
        for(SObject a : (List<Account>)Trigger.new) {
           // In this case accMap.get(a) is null as the objects stored in the map changed between contexts
           // (they now have an Id, and values for CreatedDate, CreatedById, LastModifiedDate, LastModifiedById and SystemModStamp)
            System.assertNotEquals(null, accMap.get(a), 'Value is null!');
        }
    }
}
 
trigger AccountTrigger on Account (before insert,after insert) {
    AccountTriggerHandler ath = new AccountTriggerHandler();
    if(Trigger.isBefore) {
        ath.onBefore();
    } else {
        ath.onAfter();
    }
}
 
@isTest
private class TestAccountTriggerHandler {
    static testMethod void myUnitTest() {
        List<Account> accs = new List<Account>();
        accs.add(new Account(Name ='TestAccount', BillingStreet='TestStreet1', BillingCountry='DE'));
        accs.add(new Account(Name ='TestAccount2', BillingStreet='TestStreet2', BillingCountry='DE'));
        accs.add(new Account(Name ='TestAccount3', BillingStreet='TestStreet3', BillingCountry='DE'));
        insert accs;
    }
}




promote demote