9

I already know about the equivalent of Comparable - the class to be sorted should implement Comparable and then you can sort a List of said class.

But, how about Comparator?

In java, the Collections.sort() method can take a Comparator, so that the sorting and the class implementation are separated. This enables a class to be sorted in different ways.

If I want more than one way of sorting a class, how can this be done in Apex? I have found nothing similar. I've tried extending the class and over-riding the compareTo() method, but that introduced many problems. Is there a better way?

2
  • 1
    Oh now I hate Apex. I long to return to Java one day.
    – NickJ
    Oct 13, 2015 at 17:04
  • 1
    Watch this question as sfdcfox may bring you back from the edge.....
    – Eric
    Oct 13, 2015 at 20:01

2 Answers 2

14

While Comparator isn't available out of the box, it's not terribly hard to implement. However, your performance will be considerably worse than a native solution, so you'll need to test and see how many rows you can actually sort before it becomes intolerable.

public abstract class Comparator {
    public abstract Integer compare(Object o1, Object o2);
    public static void sort(Object[] values, Comparator comp) {
        //  Obtain the list type of values
        Object[] temp = values.clone();
        temp.clear();
        //  Helper class for sorting using Comparable
        Helper[] tempValues = new Helper[0];
        for(Object value: values) {
            tempValues.add(new Helper(comp, value));
        }
        //  Perform sort
        tempValues.sort();
        //  Extract values back into temp list
        for(Helper helper: tempValues) {
            temp.add(helper.value);
        }
        //  And set the list to the new, sorted order
        values.clear();
        values.addAll(temp);
    }
    //  Simply calls Comparator when asked.
    class Helper implements Comparable {
        Comparator method;
        Object value;
        Helper(Comparator comp, Object val) {
            method = comp;
            value = val;
        }
        public Integer compareTo(Object o) {
            return method.compare(value, ((Helper)o).value);
        }
    }
}

From here, you can create your own solutions:

public class AccountNameComparator extends Comparator {
    public override Integer compare(Object a, Object b) {
        return ((Account)a).name.compareTo(((Account)b).name);
    }
}

Which would let you sort as you like:

Account[] accounts = [SELECT Name FROM Account ORDER BY CreatedDate LIMIT 5];
Comparator.sort(accounts, new AccountNameComparator());

This should work with any sort of data type, even custom classes, as long as you can implement the "compare" method. This version also doesn't take into consideration null values specifically, so you may need to adjust your implementations accordingly.

1
  • 3
    This is a clever solution! It's a shame that so much time and effort has to go into getting round Apex's many shortcomings, however.
    – NickJ
    Oct 14, 2015 at 9:59
4

As requested in the idea Support apex List sorting using a Comparator, the Winter '24 release brings a native Comparator interface and corresponding List sort method overload to work with it.

You can directly implement the Comparator for your type and then swap it out as required.

// Class to compare Accounts by name
public class AccountNameComparator implements Comparator<Account> {
    public Integer compare(Account a1, Account a2) {
        if(a1?.name == null && a2?.name == null) {
            return 0;
        } else if(a1?.name == null) { 
            return -1; 
        } else if(a2?.name == null) {
            return 1;
        }        
        return a1.name.compareTo(a2.name);
    }
}

Note that most of the effort in implementing the Comparator is actually in the null checking here. Any Comparator implementation should make considerations for null handling. Both for the objects being sorted, and the fields being specifically compared. This examples treats a null object and null name field value value as equivalent, which needs to be considered if null objects should go to the start of the sort results.

And a test class for good measure:

@IsTest
public class AccountNameComparator_Test {
    @IsTest 
    public static void sortAccounts() {
        
        List<Account> toSort = new List<Account>();
        toSort.add(new Account(Name='Test1', NumberOfEmployees=5));
        toSort.add(new Account(Name='Test3', NumberOfEmployees=2));
        toSort.add(new Account(Name='Test2'));
        insert toSort;
        
        List<Account> accounts = [SELECT Name FROM Account ORDER BY CreatedDate LIMIT 5];
        accounts.add(new Account()); // Account with no name
                    
        Test.startTest();
        accounts.sort(new AccountNameComparator());
        Assert.isNull(accounts.get(0).Name);
        Assert.areEqual('Test1', accounts.get(1).Name);
        Assert.areEqual('Test2', accounts.get(2).Name);
        Assert.areEqual('Test3', accounts.get(3).Name);           
        Test.stopTest();
    }
}

You must log in to answer this question.

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