I’m running into some weird behavior when using an Enum as a Key in a map. This same code works fine in other contexts, but not when in batch.
I have an Enum to represent currency types:
public enum CurrencyType { USD, AUD, GBP, CAD }
In an exchange client, I have a
Map<CurrencyType, Decimal>
. However, when I try toget
a value using an Enum, it’s returning null.private Map<CurrencyType, Decimal> exchangeRates; /** * Returns the exchange rate for any given currency vs the base * @param currencyType The currency to get the exchange rate for * @return The Exchange Rate for given currency */ public Decimal getExchangeRate(CurrencyType currencyType){ System.debug(currencyType); //GBP System.debug(exchangeRates); //{USD=1.0, AUD=1.3184, CAD=1.3065, GBP=0.79164} System.debug(exchangeRates.containsKey(currencyType)); //false for(CurrencyType ct : exchangeRates.keySet()){ System.debug(ct); //when == GBP system.debug(ct == currencyType); //true System.debug(ct.hashCode()); //127990640 System.debug(currencyType.hashCode()); //608648681 } return exchangeRates.get(currencyType); //returns null }
UPDATE:
Here is the Raw Debug Statements so you can see everything is populated properly:
12:50:53.0 (19696843)|USER_DEBUG|[55]|DEBUG|~Getting Exchange Rates~ 12:50:53.0 (19711834)|USER_DEBUG|[56]|DEBUG|GBP 12:50:53.0 (19752692)|USER_DEBUG|[57]|DEBUG|{USD=1.0, AUD=1.3184, CAD=1.3065, GBP=0.79164} 12:50:53.0 (19786841)|USER_DEBUG|[58]|DEBUG|false 12:50:53.0 (19805826)|USER_DEBUG|[59]|DEBUG|608648681 12:50:53.0 (19815542)|USER_DEBUG|[60]|DEBUG|~Testing vs Keyset~ 12:50:53.0 (19879783)|USER_DEBUG|[62]|DEBUG|USD 12:50:53.0 (19912155)|USER_DEBUG|[63]|DEBUG|false 12:50:53.0 (19930846)|USER_DEBUG|[64]|DEBUG|70283834 12:50:53.0 (19945440)|USER_DEBUG|[65]|DEBUG|608648681 12:50:53.0 (19973023)|USER_DEBUG|[62]|DEBUG|AUD 12:50:53.0 (20006415)|USER_DEBUG|[63]|DEBUG|false 12:50:53.0 (20026344)|USER_DEBUG|[64]|DEBUG|1746277182 12:50:53.0 (20041304)|USER_DEBUG|[65]|DEBUG|608648681 12:50:53.0 (20068693)|USER_DEBUG|[62]|DEBUG|CAD 12:50:53.0 (20097827)|USER_DEBUG|[63]|DEBUG|false 12:50:53.0 (20116138)|USER_DEBUG|[64]|DEBUG|76325736 12:50:53.0 (20130603)|USER_DEBUG|[65]|DEBUG|608648681 12:50:53.0 (20157100)|USER_DEBUG|[62]|DEBUG|GBP 12:50:53.0 (20186003)|USER_DEBUG|[63]|DEBUG|true 12:50:53.0 (20204017)|USER_DEBUG|[64]|DEBUG|127990640 12:50:53.0 (20218331)|USER_DEBUG|[65]|DEBUG|608648681
As you can see in the last two lines, the enum equals true when testing directly against the
keyset()
. HowevercontainsKey()
returnsfalse
(USER_DEBUG|[58]);Weird thing is… This definitely seems to work in other contexts. I know I could easily work around this by using
enum.name()
as the key instead of the enum itself, but I really want know whats going on here.UPDATE 2:
I added
hashCode()
details and confirmed that they are different! So basically it’s possible for two enums of the same value to generate different Hash Codes!?
Answer
I think I’ve figured it out.
In java, the hashCode for an enum is not consistent across different executions.
My Map is being initialized in the Batchable start()
(with Database.Stateful
). Then I try to access the map in Batchable execute()
.
Behind the scenes, salesforce must be running start()
& execute()
in separate java executions. This means that when the hashcode is evaluated in the start()
method it is not the same as when it is evaluated in the execute()
In short, Enums cannot be used as keys in when using Database.Stateful
.
UPDATE:
I did some testing and it only seems to be an issue when you use a query locator and only fails on the second iterator. I’ve replicated it with the following code:
global class BatchEnumTesting implements Database.Batchable<SObject>, Database.Stateful {
public Enum Foo{
A, B, C
}
private Integer i;
private Map<Foo, String> enumMap;
global BatchEnumTesting() {}
global Database.QueryLocator start(Database.BatchableContext BC) {
i = 1;
enumMap = new Map<Foo, String>();
enumMap.put(Foo.A,'Testing1');
enumMap.put(Foo.B,'Testing2');
enumMap.put(Foo.C,'Testing3');
for(Foo e : Foo.values()){
System.debug('Key: ' + e.Name() + ' | ' + e.hashCode());
}
return Database.getQueryLocator('SELECT Id FROM Account Limit 2');
}
global void execute(Database.BatchableContext BC, List<SObject> scope) {
System.debug(i++);
for(Foo e : enumMap.keySet()){
System.debug('Key: ' + e.Name());
if(e == Foo.A){
System.debug('Hash Compare: ' + e.hashCode() + ' | ' + Foo.A.hashCode());
System.debug('Map Match : ' + enumMap.containsKey(Foo.A));
}else if(e == Foo.B){
System.debug('Hash Compare: ' + e.hashCode() + ' | ' + Foo.B.hashCode());
System.debug('Map Match : ' + enumMap.containsKey(Foo.B));
}if(e == Foo.C){
System.debug('Hash Compare: ' + e.hashCode() + ' | ' + Foo.C.hashCode());
System.debug('Map Match : ' + enumMap.containsKey(Foo.C));
}
}
}
}
global void finish(Database.BatchableContext BC) {}
Run with a batch size of 1:
Database.executeBatch(new BatchEnumTesting(), 1);
Attribution
Source : Link , Question Author : NSjonas , Answer Author : Community