Image generated by Stable Diffusion from a prompt by Bob Buzzard |
Introduction
The Winter 24 release of Salesforce introduces a few new Apex features, including one that I'm very pleased to see - the Comparator interface. Simply put, this new interface allows the List.sort() method to take a parameter that determines the sort order of the elements in the List.
The Problem
Now this might not sound like a big change, but it simplifies the support of sorting Lists quite a bit. The way we used to have to do it was for the items in the List to implement the Comparable interface. I've written loads of custom Apex classes over the years that implement this, and it's very straightforward - here's an example from a class that retrieves the code coverage values for all Apex classes in an org and displays them in order of coverage with the lowest covered (problem!) classes first :
public Integer compareTo(Object compareTo) { CoverageRecord that=(CoverageRecord) compareTo; return this.getPercentage()-that.getPercentage(); }In this case, implementing compareTo isn't any real overhead - I've created a custom class that contains a whole bunch of information about the coverage for an Apex class - total lines, lines covered etc, so an extra method with a couple of lines of code isn't a big deal. It's a little less convenient if I need to sort a class from an App Exchange package - in that case I'll need to create a new class from scratch to wrap the packaged class and implement the method. If we assume that my coverage class is now in a package, it would look something like :
public class CoverageWrapper { public BBCOVERAGE__CoverageRecord coverage {get; set;} public Integer compareTo(Object compareTo) { BBCOVERAGE__CoverageRecord that=(BBCOVERAGE__CoverageRecord) compareTo; return this.coverage.getPercentage()-that.getPercentage(); } }
Slightly less convenient - I now have a whole new class to maintain to be able to sort, and I have to store all the elements of the list in a CoverageWrapper rather than their original CoverageRecord. Again, not a huge amount of overhead but it gets a bit samey if I'm doing a lot of this kind of thing.
Much the same thing applies if I want to sort sObjects - I need to create a wrapper class and turn my list of sObjects into a list of the wrapper class before I can sort it. All those CPU cycles gone forever!
The Solution
This all changes in Winter 24 with the Comparator interface. I still need to create a class that implements an interface - the Comparator in this case :
public with sharing class CoverageComparator implements Comparator<CoverageRecord> { public Integer compare(CoverageRecord one, CoverageRecord tother) { return one.getPercentage()-tother.getPercentage(); } }
but I don't need to wrap the class/sObject that I am processing in this class and create a new list. Instead I call the new sort() method that takes a Comparator parameter:
List<CoverageRecord> coverageRecords; ... CoverageComparator covComp=new CoverageComparator(); coverageRecords.sort(covComp);
CPU Impact
List<CoverageRecord> covRecs=TestData.CreateRecords(100); Integer startCpu=Limits.getCpuTime(); CoverageComparator covComp=new CoverageComparator(); covRecs.sort(covComp); Integer stopCpu=Limits.getCpuTime(); System.debug(LoggingLevel.ERROR, 'CPU for comparator = ' + (stopCpu-startCpu));
And secondly wrapping them with classes that implement Comparable:
List<CoverageRecord> covRecs=TestData.CreateRecords(100); Integer startCpu=Limits.getCpuTime(); List<CoverageWrapper> wrappers=new List<CoverageWrapper>(); for (CoverageRecord covRec : covRecs) { CoverageWrapper wrapper=new CoverageWrapper(); wrapper.coverage=covRec; wrappers.add(wrapper); } wrappers.sort(); Integer stopCpu=Limits.getCpuTime(); System.debug(LoggingLevel.ERROR, 'CPU for comparable = ' + (stopCpu-startCpu));
The results were broadly what I was expecting, as sorting a list in place is always going to be quicker than iterating it, wrapping the members, and then sorting, but it's always good to see the numbers:
- For 100 records, Comparator took 9 milliseconds versus 11 milliseconds for wrapping
- For 1,000 records, Comparator took 126 milliseconds versus 150 for wrapping
- For 10,000 records, Comparator took 1844 millseconds versus 2058 for wrapping