2

We usually have need for a Map<Id, Integer> counter which does the following:

for (thing t : things) {
    if(thingCountMap.containsKey(t.id)){   
        thingCountMap.put(t.id,thingCountMap.get(t.id)+1);
    }
    else{
        thingCountMap.put(t.id,1);
    } 
}

Is there an apex built-in or some other pattern I should use here? I can probably wrap this into a CounterMap but didn't know if there was a Namespace or a util class out there that does this already.

P.S. The developer was using this pattern to get around SOQL execution limits in a batchified apex class.

2 Answers 2

5

Thanks to the new Null Coalescing Operator operator, you can write this as such:

for(thing t: things) {
  thingCountMap.put(t.id, (thingCountMap.get(t.id) ?? 0) + 1);
}

I'm not aware of a built-in class that does anything like this, but this should be simple enough.

1

There is no built-in Counter.

I'm assuming that you're trying to do something like count the number of child records related to a given parent. You haven't provided enough context to know if there's a more sensible way to go about things though.

It is a bit annoying to update primitive types in a map (because they're not stored by reference, so you need to .get() and .put()), but other approaches likely wouldn't save you any amount of typing nor would they be likely to execute any faster.

Using the null coalescing operator introduced in API v60.0 (Spring '24) and a List<Boolean> for the map value would probably be the best shot at balancing code length, readability, and heap usage

Map<Id, List<Boolean>> listById = new Map<Id, List<Boolean>>();

for(Thing t : thingList) {
    List<Boolean> workingList = listById.get(t.Id) 
        ?? listById.put(t.Id, new List<Boolean>()) 
        ?? listById.get(t.Id);

    workingList.add(true);
}

// you'd get the count by looking at the size of the list for a given Id
//Integer count = listById.get(t.Id)?.size() ?? 0;

The listById.put(t.Id, new List<Boolean>()).get(t.Id); looks a bit funky, but that prevents us from needing to explicitly put an if check in the loop that would be checked for every iteration.

I don't know whether or not the ?? is implicitly doing an if(x == null){ x } else { y } behind the scenes, but if nothing else it does save a little bit of typing.

6
  • The left hand value (l-value) is evaluated first, then compared to null, and if null, then the right hand value (r-value) is evaluated. If the l-value is not null, the r-value is never evaluated. This matters if the values are the result of a function call or getter, which may have side effects. So, it's more fair to say: Object temp = x; if(temp == null) temp = y; return temp;. The l-value is evaluated only once, and the r-value is evaluated either zero or one times.
    – sfdcfox
    Commented Mar 14 at 16:21
  • @derek F I don't know why you got downvoted but I appreciate the effort in this answer.
    – Shaun
    Commented Mar 14 at 16:29
  • I think what makes this helpful beyond the saving typing I can cover a MapUtility.Counter with unit tests once and reuse it in other places.
    – Shaun
    Commented Mar 14 at 16:30
  • 1
    @Shaun p.s. You don't need to mention the owner of a post (question or answer); they'll get a notification automatically.
    – sfdcfox
    Commented Mar 14 at 16:45
  • and I've realized that the map.put().get() thing won't work (because .put() doesn't return the map itself). I'll try to think about this one a bit more.
    – Derek F
    Commented Mar 14 at 21:24

You must log in to answer this question.

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