Sorting Lists of sObjects

Using the List.sort method, you can sort lists of sObjects.

For sObjects, sorting is in ascending order and uses a sequence of comparison steps outlined in the next section. You can create a custom sort order for sObjects by wrapping your sObject in an Apex class that implements the Comparable interface. You can also create a custom sort order by passing a class that implements Comparator as a parameter to the sort method. See Custom Sort Order of sObjects.

Default Sort Order of sObjects

The List.sort method sorts sObjects in ascending order and compares sObjects using an ordered sequence of steps that specify the labels or fields used. The comparison starts with the first step in the sequence and ends when two sObjects are sorted using specified labels or fields. The following is the comparison sequence used:
  1. The label of the sObject type.

    For example, an Account sObject appears before a Contact.

  2. The Name field, if applicable.

    For example, if the list contains two accounts named Alpha and Beta, account Alpha comes before account Beta.

  3. Standard fields, starting with the fields that come first in alphabetical order, except for the Id and Name fields.

    For example, if two accounts have the same name, the first standard field used for sorting is AccountNumber.

  4. Custom fields, starting with the fields that come first in alphabetical order.

    For example, suppose two accounts have the same name and identical standard fields, and there are two custom fields, FieldA and FieldB, the value of FieldA is used first for sorting.

Not all steps in this sequence are necessarily carried out. For example, a list containing two sObjects of the same type and with unique Name values is sorted based on the Name field and sorting stops at step 2. Otherwise, if the names are identical or the sObject doesn’t have a Name field, sorting proceeds to step 3 to sort by standard fields.

For text fields, the sort algorithm uses the Unicode sort order. Also, empty fields precede non-empty fields in the sort order.

Here’s an example of sorting a list of Account sObjects. This example shows how the Name field is used to place the Acme account ahead of the two sForce accounts in the list. Since there are two accounts named sForce, the Industry field is used to sort these remaining accounts because the Industry field comes before the Site field in alphabetical order.

1Account[] acctList = new List<Account>();        
2acctList.add( new Account(
3    Name='sForce',
4    Industry='Biotechnology',
5    Site='Austin'));
6acctList.add(new Account(
7    Name='sForce',
8    Industry='Agriculture',
9    Site='New York'));
10acctList.add(new Account(
11    Name='Acme'));
12System.debug(acctList);
13
14acctList.sort();
15Assert.areEqual('Acme', acctList[0].Name);
16Assert.areEqual('sForce', acctList[1].Name);
17Assert.areEqual('Agriculture', acctList[1].Industry);
18Assert.areEqual('sForce', acctList[2].Name);
19Assert.areEqual('Biotechnology', acctList[2].Industry);
20System.debug(acctList);

This example is similar to the previous one, except that it uses the Merchandise__c custom object. This example shows how the Name field is used to place the Notebooks merchandise ahead of Pens in the list. Because there are two merchandise sObjects with the Name field value of Pens, the Description field is used to sort these remaining merchandise items. The Description field is used for sorting because it comes before the Price and Total_Inventory fields in alphabetical order.

1Merchandise__c[] merchList = new List<Merchandise__c>();        
2merchList.add( new Merchandise__c(
3    Name='Pens',
4    Description__c='Red pens',
5    Price__c=2,
6    Total_Inventory__c=1000));
7merchList.add( new Merchandise__c(
8    Name='Notebooks',
9    Description__c='Cool notebooks',
10    Price__c=3.50,
11    Total_Inventory__c=2000));
12merchList.add( new Merchandise__c(
13    Name='Pens',
14    Description__c='Blue pens',
15    Price__c=1.75,
16    Total_Inventory__c=800));
17System.debug(merchList);
18
19merchList.sort();
20Assert.areEqual('Notebooks', merchList[0].Name);
21Assert.areEqual('Pens', merchList[1].Name);
22Assert.areEqual('Blue pens', merchList[1].Description__c);
23Assert.areEqual('Pens', merchList[2].Name);
24Assert.areEqual('Red pens', merchList[2].Description__c);
25System.debug(merchList);

Custom Sort Order of sObjects

To create a custom sort order for sObjects in lists, implement the Comparator interface and pass it as a parameter to the List.sort method.

Alternatively, create a wrapper class for the sObject and implement the Comparable interface. The wrapper class contains the sObject in question and implements the Comparable.compareTo method in which you specify the sort logic.

Example

This example implements the Comparator interface to compare two opportunities based on the Amount field.

1public class OpportunityComparator implements Comparator<Opportunity> {
2    public Integer compare(Opportunity o1, Opportunity o2) {
3        // The return value of 0 indicates that both elements are equal.
4        Integer returnValue = 0;
5        
6        if(o1 == null && o2 == null) {
7            returnValue = 0;
8        } else if(o1 == null) {
9            // nulls-first implementation
10            returnValue = -1; 
11        } else if(o2 == null) {
12            // nulls-first implementation
13            returnValue = 1;
14        } else if ((o1.Amount == null) && (o2.Amount == null)) {
15            // both have null Amounts
16            returnValue = 0;
17        } else if (o1.Amount == null){
18            // nulls-first implementation
19            returnValue = -1;
20        } else if (o2.Amount == null){
21            // nulls-first implementation
22            returnValue = 1;
23        } else if (o1.Amount < o2.Amount) {
24            // Set return value to a negative value.
25            returnValue = -1;
26        } else if (o1.Amount > o2.Amount) {
27            // Set return value to a positive value.
28            returnValue = 1;
29        }
30        return returnValue;
31    }
32}

This test sorts a list of Comparator objects and verifies that the list elements are sorted by the opportunity amount.

1@isTest
2private class OpportunityComparator_Test {
3 
4    @isTest
5    static void sortViaComparator() {
6        // Add the opportunity wrapper objects to a list.
7        List<Opportunity> oppyList = new List<Opportunity>();
8        Date closeDate = Date.today().addDays(10);
9        oppyList.add( new Opportunity(
10            Name='Edge Installation',
11            CloseDate=closeDate,
12            StageName='Prospecting',
13            Amount=50000));
14        oppyList.add( new Opportunity(
15            Name='United Oil Installations',
16            CloseDate=closeDate,
17            StageName='Needs Analysis',
18            Amount=100000));
19        oppyList.add( new Opportunity(
20            Name='Grand Hotels SLA',
21            CloseDate=closeDate,
22            StageName='Prospecting',
23            Amount=25000));
24        oppyList.add(null);
25        
26        // Sort the objects using the Comparator implementation
27        oppyList.sort(new OpportunityComparator());
28        // Verify the sort order
29        Assert.isNull(oppyList[0]);
30        Assert.areEqual('Grand Hotels SLA', oppyList[1].Name);
31        Assert.areEqual(25000, oppyList[1].Amount);
32        Assert.areEqual('Edge Installation', oppyList[2].Name);
33        Assert.areEqual(50000, oppyList[2].Amount);
34        Assert.areEqual('United Oil Installations', oppyList[3].Name);
35        Assert.areEqual(100000, oppyList[3].Amount);
36        // Write the sorted list contents to the debug log.
37        System.debug(oppyList);
38    }
39}

Example

This example shows how to create a wrapper Comparable class for Opportunity. The implementation of the compareTo method in this class compares two opportunities based on the Amount field—the class member variable contained in this instance, and the opportunity object passed into the method.

1public class OpportunityWrapper implements Comparable {
2
3    public Opportunity oppy;
4    
5    // Constructor
6    public OpportunityWrapper(Opportunity op) {
7    	// Guard against wrapping a null 
8    	if(op == null) {
9    		Exception ex = new NullPointerException();
10    		ex.setMessage('Opportunity argument cannot be null'); 
11    		throw ex;
12    	}
13        oppy = op;
14    }
15    
16    // Compare opportunities based on the opportunity amount.
17    public Integer compareTo(Object compareTo) {
18        // Cast argument to OpportunityWrapper
19        OpportunityWrapper compareToOppy = (OpportunityWrapper)compareTo;
20        
21        // The return value of 0 indicates that both elements are equal.
22        Integer returnValue = 0;
23        if ((oppy.Amount == null) && (compareToOppy.oppy.Amount == null)) {
24            // both wrappers have null Amounts
25            returnValue = 0;
26        } else if ((oppy.Amount == null) && (compareToOppy.oppy.Amount != null)){
27            // nulls-first implementation
28            returnValue = -1;
29        } else if ((oppy.Amount != null) && (compareToOppy.oppy.Amount == null)){
30            // nulls-first implementation
31            returnValue = 1;
32        } else if (oppy.Amount > compareToOppy.oppy.Amount) {
33            // Set return value to a positive value.
34            returnValue = 1;
35        } else if (oppy.Amount < compareToOppy.oppy.Amount) {
36            // Set return value to a negative value.
37            returnValue = -1;
38        } 
39        return returnValue;
40    }
41}

This test sorts a list of OpportunityWrapper objects and verifies that the list elements are sorted by the opportunity amount.

1@isTest 
2private class OpportunityWrapperTest {
3    static testmethod void test1() {
4        // Add the opportunity wrapper objects to a list.
5        OpportunityWrapper[] oppyList = new List<OpportunityWrapper>();
6        Date closeDate = Date.today().addDays(10);
7        oppyList.add( new OpportunityWrapper(new Opportunity(
8            Name='Edge Installation',
9            CloseDate=closeDate,
10            StageName='Prospecting',
11            Amount=50000)));
12        oppyList.add( new OpportunityWrapper(new Opportunity(
13            Name='United Oil Installations',
14            CloseDate=closeDate,
15            StageName='Needs Analysis',
16            Amount=100000)));
17        oppyList.add( new OpportunityWrapper(new Opportunity(
18            Name='Grand Hotels SLA',
19            CloseDate=closeDate,
20            StageName='Prospecting',
21            Amount=25000)));
22        
23        // Sort the wrapper objects using the implementation of the 
24        // compareTo method.
25        oppyList.sort();
26        
27        // Verify the sort order
28        Assert.areEqual('Grand Hotels SLA', oppyList[0].oppy.Name);
29        Assert.areEqual(25000, oppyList[0].oppy.Amount);
30        Assert.areEqual('Edge Installation', oppyList[1].oppy.Name);
31        Assert.areEqual(50000, oppyList[1].oppy.Amount);
32        Assert.areEqual('United Oil Installations', oppyList[2].oppy.Name);
33        Assert.areEqual(100000, oppyList[2].oppy.Amount);
34        
35        // Write the sorted list contents to the debug log.
36        System.debug(oppyList);
37    }
38}