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
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:
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
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
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
Map.containsKey. Interestingly, List operations like
List.contains solely rely on
equals and never on
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
Thanks to the Iterator’s
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.
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:
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
- > 0 if
thisinstance is greater than
- < 0 if
thisinstance is less than
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
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
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
- Call the
- Convert the
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.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
Comparableobject 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
This pattern can easily be replicated in Apex with a custom
ListUtils class and a
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.
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.
- 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.