This topic was originally presented by Philippe Ozil during an Apex Hours session on September 4, 2021.
Working with collections like List, Map, and Set is part of an everyday routine for Apex developers. While their basic use is straightforward, there are some advanced quirks that can get you to the next level. In this post, we’ll start with the basics about objects and collections, then we’ll dive into advanced concepts, such as iterators and sorting.
Exploring objects and collections
About objects and their methods
In Apex, all classes and primitives inherit from the Object class. This inheritance is implicit in the sense that you do not need to specify it when creating a custom class, for instance.
Thanks to the inheritance from Object, all object instances can use the equals
, hashCode
, and toString
methods as demonstrated here:
equals
let you compare an object instance for equality with another one. What’s interesting with this method is that it lets you compare object of different types. For instance, you can compare an Id with a String:
While equals
is powerful, it has a performance cost when running on objects with large number of properties. This is where the hashCode
method comes into play. hashCode
returns an integer that identifies an object instance based on some of its property values. For example, the integer 123 has a hash code of 123 and a “Hello World” string has a hash code of -862545276.
Hash code values are pseudo-unique as some object instances with different property values may share the same hash code. This is called hash code collision and is considered acceptable. When a collision happens, equals
is used as a fallback to compare objects. In any case, comparing two objects’ hash codes is statistically faster than calling equals
.
The equals
and hashCode
methods are essential when working with collections, and we’ll see why as we explore the different collection types.
An overview of collection types
Object instances can be stored in three types of collections: List, Set, and Map. These collection types have different purposes and properties:
- A List holds an ordered collection of non-unique elements
- A Set holds an unordered collection of unique elements
- A Map holds a dictionary of key values in which keys are unique but values are not
Fun fact: Set and Map are named after mathematical concepts (see Set and Map definitions).
List and Set have some degree of interoperability as they share some constructors and methods that work with both types. For example, you can build a List from a Set and vice-versa, and you can call methods like addAll
with both types. This enables powerful uses cases, such as removing duplicate elements from a List by casting it into a Set:
The unicity of Set values and Map keys is enforced thanks to the hashCode
method and a fallback to equals
in case of hash code collision. These two methods are also essential for some Set and Map operations, such as Set.contains
or Map.containsKey
. Interestingly, List operations like List.contains
solely rely on equals
and never on hashCode
.
Now that we’ve seen the big picture on objects and collection types, let’s dive into collection specifics. We’ll start by iterating on Lists and Sets.
Iterating on Lists and Sets
List and Set can be traversed with for
loops, but there’s a powerful alternative: iterators. List and Set implement the Iterable
interface, and this interface provides access to an iterator
method that exposes an object that implements the Iterator
interface.
Thanks to the Iterator’s hasNext
and next
methods, you can traverse a collection in a single direction:
While the above code is more verbose than for
loops, using iterators has three key advantages:
- Iterators provide read-only access to collections. If you pass an iterator as a method parameter, you’re sure that this method cannot modify your collection.
- Iterators lock the collection in read-only mode, preventing any modification to the collection while you are iterating on it:
- Iterators provide a mechanism that let you dynamically retrieve elements on the go. Our core focus in this article is collections, but you can implement your own iterable classes. This is extremely useful when working with paginated data and when you retrieve elements in batches. For example, you could build a REST client that iterates on a resource, and you could start iterating without knowing the exact number of items that are available.
Check out iteration recipes in Apex Recipes for iterator examples along with their related test class.
Sorting Lists
The first choice when it comes to sorting records is generally to write a SOQL query with an ORDER BY clause. However, sometimes you need to sort records without fetching them from the database or even sort objects that are not records. In those cases, you can use the List.sort method like this:
The List.sort
method is easy to use, but let’s take a closer look at how it works and how you can customize the ordering.
Working with the Comparable interface
List.sort
works with List elements that implement the Comparable interface. The interface specifies a single method: compareTo
that is called by the List sorting algorithm to order elements.
compareTo
returns an integer with the following values:
- 0 if
this
instance andobjectToCompareTo
are equal - > 0 if
this
instance is greater thanobjectToCompareTo
- < 0 if
this
instance is less thanobjectToCompareTo
List.sort
can sort any mix of objects from various types as long as they are primitives or they implements Comparable
. For instance, you could perfectly write something like this:
As a word of caution, it’s safe to place objects that don’t implement Comparable
in a List, but if you call the sort
method on that List, you’ll get a System.ListException
exception.
The sort
method documentation provides an example implementation for sorting a custom class, so we won’t dive in details on this topic. But what about sorting SObject
lists (list of records)?
Sorting lists of sObjects
SObject
implements the Comparable
interface and instances of that class have a predictable sort order. However, you may need to implement a custom sort order in some cases, and this is where things get more complex. The SObject class is final, so you cannot overwrite its internal methods, such as compareTo
.
To compensate for that, you must work with a wrapper class around the SObject
that you want to sort. Imagine that you are importing some accounts from a third-party integration and that you want to sort them based on the shipping country field before saving them. You can’t sort the records with SOQL since they are not yet in the database. You must implement the following class:
You could sort your List with your custom ordering logic in three steps:
- Convert the
List<Account>
into aList<SortableAccount>
- Call the
sort
method onList<SortableAccount>
- Convert the
List<SortableAccount>
back toList<Account>
However, implementing these steps is not practical as it would almost take as many lines of code as the implementation of SortableAccount
and those extra lines wouldn’t be reusable. Fortunately, there’s something that you can do to reduce boilerplate code. Simply add the following static method to SortableAccount
:
With this SortableAccount.sort
static method, all it takes to sort a list of account records by shipping country is a single line:
Check out List recipes in Apex Recipes for the implementation of SortableAccount
along with the related test class.
Sorting Lists with reusable comparators
While the default List.sort
method is convenient, it has two important limitations:
- The ordering logic is directly tied to the
Comparable
object that is being sorted - The sort method lacks the ability to sort with different strategies and parameters
The Java language (which is close to Apex) goes beyond the basic Apex sort method and exposes a convenient Arrays.sort(T[], Comparator)
method where T
is the type being sorted and Comparator
an interface that specifies a compare
method that works like Comparable.compareTo
.
This pattern can easily be replicated in Apex with a custom ListUtils
class and a Comparator
interface:
With this approach, you can sort Lists with different comparators and even pass parameters to comparators. You benefit from the fact that the ordering logic is decoupled from the objects that you sort.
Check out List recipes in Apex Recipes for the implementation of ListUtils
, some comparators, and related test classes.
Closing words
That’s a wrap. We gave you a refresher on the Object class and the different collection types. We covered advanced collection concepts: you’ve learned how to use iterators and how to sort Lists with the default sort method and custom comparators. We hope this article helped to improve your understanding of collections. It’s now your turn to put this knowledge into practice in your projects.
Resources
- Apex Recipes
- Apex developer guide
- Apex reference guide
- Java documentation for hashCode and equals methods
About the author
Philippe Ozil is a Principal Developer Advocate at Salesforce where he focuses on the Salesforce Platform. He writes technical content and speaks frequently at conferences. He is a full stack developer and enjoys working on DevOps, robotics, and VR projects. Follow him on Twitter @PhilippeOzil or check his GitHub projects @pozil.