Sorting Apex classes SObject using apex-lang code share project!

You must be stuck with Apex when sorting is required on instances of user-defined classes and pre-loaded Sobjects. Unfortunately, the standard Apex API just gives a method List.sort(), which only works with primitive data types.

So to get the job done, developers can implement their own sorting, either using standard and simple bubble sort or some other complex sorting algorithm. But this approach is not good because:

  • Apex Governor gives a handful of script statements and limited stack depth for your business logic.

  • Bubble sort is never a preferred sorting algorithm for performance reasons (complexity) and several high iterations. 

  • Implementing some complex sorting algorithms is,

    • Difficult.

    • Error-prone.

    • Mostly suited to one user-defined type or sobject.

So, what’s the solution?

We can’t do anything about governor; best would be to let developers focus on business logic only and let sorting be taken care of by some other library, like apex-lang.

In brief, apex-lang is a part of the popular Java “Apache Common Lang” project for Apex. More details about apex-lang are available here on Google Code Project.

Apex-lang offers many Apex utility classes. One of them is ArrayUtils; it offers APIs to quickly sort instances of SObject and Object (user-defined types/classes). The sorting API is very similar to Java’s Collections.sort (list, comparator), where you can pass in a “Comparator” to compare fields of two instances for a “class”.

Sorting using Apex-Lang

The first prerequisite for sorting using apex-lang is implementing a Comparator. Those who are from a Java background, for sure know about comparators and their importance in sorting. For those who are not, no need to panic. 

The comparator has a single method: Integer compare(Object o1, Object o2)

Implementation of this method, should just “compare its two arguments for order. And return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.”

How do I create a Comparator?

For the sake of explanation, let's assume we have a user-defined class called "Word" that encapsulates a single English word. As shown below:

  public class Word {
    // the real word string
    public String val {get; set;}
    public Word(String theWord) {
      if (theWord == null) theWord = '';
      val = theWord;
    }
  }

Next, we will create two comparators implementation

  1. One, that sorts the words alphabetically.

  2. Second, that sorts the words based on their length.

To start, we just need to extend the ObjectComparator Apex class available in apex-lang library. Below is a Comparator that sorts the instances of the above Word class alphabetically!

It is quite simple, just 3 lines of code:

  global class WordComparator implements ObjectComparator {
    global Integer compare(Object o1, Object o2) {
      // Note we used Apex String.compareTo(String1, String2) method here
      // as it does the same i.e. return Integer based on
      // String comparision
      return ((Word)o1).val.compareTo(((Word)o2).val);
    }
  }

Next is the comparator implementation that will be used to sort the words by length. Again, a 3-liner code.

  global class WordSizeComparator implements ObjectComparator {
    global Integer compare(Object o1, Object o2) {
      // Note: we can just subtract length of two Strings 
      // to compare them
      return (((Word)o1).val.length()) - (((Word)o2).val.length());
    }
  }

Note: The above comparator implementations are good for user-defined apex classes only. If you want to sort instances of SObject, then instead of implementing ObjectComparator, just implement ISObjectComparator. Everything else remains the same. We need two different comparators here because, unlike Java, Apex List<SObject> doesn’t sit well with List<Object>.

Here is a sample Sobject comparator for "Contact" that sorts it on LastName. You must be thinking we can load the contacts, by just querying using ORDER BY in SOQL, but trust me, it's not always possible (Governor limits!).

  global class ContactLastNameComparator implements ISObjectComparator {
    global Integer compare(SObject o1, SObject o2) {
      // Note we used Apex String.compareTo(String1, String2) method here
      // as it does the same i.e. return Integer based on
      // String comparision
      return ((Contact)o1).LastName.compareTo(((Contact)o2).LastName);
    }
  }

How to sort using the Comparator?

Once Comparator is implemented, you can start sorting using apex-lang’s:

  • ArrayUtils.qsort(List<Object> theList, ObjectComparator comparator), to sort in ascending.

  • or, ArrayUtils.qsort(List<Object> theList, ObjectComparator comparator, Boolean sortAsc), to sort in descending.

OR, to sort instances of Sobject, use:

  • ArrayUtils.qsort(List<Sobject> theList, ISObjectComparator comparator), to sort in ascending.

  • or, ArrayUtils.qsort(List<Sobject> theList, ISObjectComparator comparator, Boolean sortAsc), to sort in descending.

Sorting Word class or User-defined type/classes?

For example, to sort instances of the Word class created above, all we need to do is:

List wordObjects =  // Initalize list as required;
// Sort words alphabetically 
ArrayUtils.qsort(wordObjects,new WordComparator());
// Use in this manner to sort on
// the word size/length
ArrayUtils.qsort(wordObjects,new WordSizeComparator());

Note: We created WordComparator and WordSizeComparator classes above. And just instantiated it and passed it to ArrayUtils.qsort() API.

Sorting SObjects like Contact

Contact [] cons = [select Id, LastName from Contact ];
// Note we used ContactLastNameComparator
// created above
ArrayUtils.qsort(cons, new ContactLastNameComparator());

How to implement Comparators for Date, Number, and other primitives.

Apex-lang comes with a good sample comparator called PrimitiveComparator; this class shows how to compare each primitive data type. I suggest using this class from a learning standpoint, rather than using it. Because this class is meant to be too generic and it does a lot of checks and instances, etc. So you might end up giving too much share of your governor limits, like script statements.

Many other pre-build and example Comparator implementations are available as apex classes in apex-lang project. For ex. DecimalRangeComparator, SelectOptionComparator, SObjectSortByNameComparator. You can use these as a starting point to learn how to code different comparators. In case of any issues, feel free to comment on this post. I will for sure help you in writing the correct comparator.

When apex-lang can harm you?

There’s a popular saying that “With great power comes great responsibility." The same very well applies here with apex-lang; developers get a really handy way to sort almost any object(Object/SObject). But do not forget, apex-lang isn’t a part of the standard apex API; it's a collection of standard apex classes. So any governor limit that applies to your Apex code, is equally applicable on Apex-lang. The most important of these governor limits are,

  • Number of script statements used.

  • Maximum stack depth allowed.

As we all know, operations like sorting involve many iterations/recursions and comparisons. So it is very likely that, when sorting a big data set, you may end up hitting any of these governor limits.

So before relying super heavily on apex-lang sorting, a developer needs to check the following:

  1. How many script statements and other governor limits quotas are required to process the rest of the business logic?

  2. How big will be the collection to sort via apex-lang? This is important because the bigger the collection, the higher the governor limits used.

  3. Sorting 1000 objects roughly takes at least 50,000+ script statements. This figure will vary hugely, depending on comparator implementation. In the above sample comparators with 1000 words:

    • WordComparator consumed 51,792 script statements

    • WordSizeComparator consumed 1,10,654 script statements. With 1,091 words, a crash occurs with this reason: “System.LimitException: Maximum stack depth reached: 173

So, based on the above two points, here is the basic rule for developers to best avoid breaking governor limits in production/UAT.

  • Match the governor limits consumed by business logic to the sorting logic. If the business logic is already consuming too much governor quota like script lines + stack depth. Then you need to limit the amount of data sorted using apex-lang (or pick some other alternate). 

  • A developer has to know what can be the maximum collection size to be sorted. Based on this max count, the business logic should be executed to see if we are anywhere close to breaking governor limits.

To see how close you are to the limit, you can use the Limits class provided by the Apex standard library. It gives pretty handy methods like getScriptStatements(), that you can use before and after costly calls, as shown below.

Integer scriptsBefore = Limits.getScriptStatements();
ArrayUtils.qsort(wordObjects,new WordComparator());
Integer scriptsAfter = Limits.getScriptStatements();
System.debug(LoggingLevel.info, ' ##SCRIPT STATEMENTS USED ' + (scriptsAfter - scriptsBefore));

How did I benchmark “apex-lang” with a big real data set?

Programmatically, creating a big data set with real random words is pretty hard to achieve, unless you apply some randomization logic to generate it. For my benchmarking purposes. I did a simple trick, i.e.

  1. Hit any blog or news portal, like https://www.nytimes.com/.

  2. Copy the text of a blog post or article to some text editor. This will give you many random words, easily in thousands.

  3. Split those words on whitespace, to get an Apex String array of size equal to the number of words.

I used that string array of words to create instances of “word” class explained above, and that big random collection was used on different sizes to benchmark apex-lang. Hope it helps.

Why Apex-Lang should be matured to standard Apex API

Because of this governor nightmare, how cool would it be, if some of these super helpful apex-lang classes became part of Standard Apex API. So for this reason, I will post this as an idea on Salesforce Idea Exchange.

References

Let’s Talk

Drop a note below to move forward with the conversation 👇🏻

Abhinav Gupta

First Indian Salesforce MVP, rewarded Eight times in a row, has been blogging about Salesforce, Cloud, AI, & Web3 since 2011. Founded 1st Salesforce Dreamin event in India, called “Jaipur Dev Fest”. A seasoned speaker at Dreamforce, Dreamin events, & local meets. Author of many popular GitHub repos featured in official Salesforce blogs, newsletters, and books.

https://abhinav.fyi
Previous
Previous

Batch Apex & First error: Attempt to de-reference a null object !

Next
Next

Why apex-lang for Apex developers ?