Lists, Sets, Maps

List is an ordered data type that holds multiple values. Each value has its place on the list where the counting starts from 0. Here is the syntax:

List<dataType> variableName = new List<dataType>();
  • List<dataType> defines what kind of data will the list hold
  • variableName defines the name used to refer to this list
  • new defines we are making a new list
  • List<dataType>() defines we are making a blank list.

To add a value in the List we follow the syntax:

variableName.add(value);

Here is the code in action:

List<String> names = new List<String>();

names.add('Harshdeep');
names.add('Cobey');
names.add('Zerab');

System.debug(names);

If we want to access a specific element in the List, we use the following syntax:

 variableName[n];
  • variableName is the name of the list
  • [n] is the number element we are trying to access, enclosed within square brackets

Let's see this in practice:

List<String> names = new List<String>();

names.add('Harshdeep');
names.add('Cobey');
names.add('Zerab');

System.debug(names[3]);

You will be presented with the following error:

Line: 7, Column: 1
System.ListException: List index out of bounds: 3

This is because all counting starts from 0, so to access the third element, we need to subtract 1 from it because internally the list is stored in the following way:

Index Name
0 Harshdeep
1 Cobey
2 Zerab

Now let's fix the code by removing the last line with:

System.debug(names[0]);
System.debug(names[2]);

Now the log outputs the first and last name! This is super important to remember. All counting starts from 0.

Set is an unordered list that doesn't hold any duplicates. Here is how you make a Set:

Set<dataType> variableName = new Set<dataType>();
  • Set<dataType> is to declare Set of dataType type.
  • variableName is to refer to this set.
  • new defines this is a new variable.
  • Set<dataType>() is to init the empty set with said data type.

Let's see Set in action:

Set<String> names = new Set<String>();
names.add('Harshdeep');
names.add('Cobey');
names.add('Zerab');
names.add('Cobey');

System.debug(names);

When you look at the logs, the duplicate name has been removed, and the order has been changed too. Sets usually aren't used a lot in practical programming as much as Lists or Maps. A practical use for Sets would be in a scenario when bad data has been entered, and instead of exporting all data and running through a spreadsheet, all the data is stored in a set and unique values are immediately pulled out to restructure the data.

Map holds key value pairs. This means, every element will have two values. The syntax for making sets is:

Map<dataType1, dataType2> variableName = new Map<dataType1, dataType2>();
  • Map<dataType1, dataType2> is to initialize the map variable. dataType 1 and 2 can be the same data types, or different.
  • variableName is the name of the variable to refer to new is to declare it's a new variable
  • Map<dataType1, dataType2>() is to initialize an empty Map of the same data type.
  • Map<dataType1, dataType2> variableName = new Map<dataType1, dataType2>();
    variableName.put(key, value);

    You insert values in your Map by using mapVariable.put(key, value);, where key and value are dataType variables as defined in their declaration. An easier way to understand is ordering food in a drive through. You ask for Number 1, 2 and 6 and you're given the food corresponding to those numbers.

    To access value from a Map, we use .get(key).
    An example of maps with the same data type:

    Map<String, String> placeCapital = new Map<String, String>();
    placeCapital.put('New Delhi', 'India');
    placeCapital.put('Captial', 'Country');
    
    String ourCapital = placeCapital.get('New Delhi');
    System.debug(ourCapital);

    This will return "India" in your log.

    Understanding when to use Sets, Lists and Maps

    Set

    In my experience, I've rarely used Sets. Two very distinct problems were easier to solve with Set than anywhere else:

    1. An org had Billing Country field set to Text instead of the picklist, and for every country, there were multiple values, some recurring more than others. Instead of exporting data to a spreadsheet, run formulas and uploading them, it was more efficient to write a Set which gathered all the unique values in one table and then processing on it further to fix the issue.
    2. An org had a custom-built mailing list with no restrictions on the number of times you could sign up for. When certain managers would miss out on emails, they chose to re-sign up, resulting in sending multiple emails to the same user. A quick way to solve this was to make a loop and assign all values from the List to the Set and updating the final List.

    In both of these cases, it was a result of bad configuration by admins and developers which could have been easily avoided.

    To understand optimal usage for Lists v/s Maps first let us learn about For Loops.

    For Loops

    For loops is one of the most commonly used code blocks in Apex programming. A For loop can be used in 2 different ways:

    1. To run a code block n number of times
    2. To iterate over a list

    Running a code block n times

    To make this loop, we need three things:

    1. A start statement
    2. An exit condition
    3. An increment statement

    The syntax is:

    for(start statement; exit condition; increment statement) {
      //code block
    }
    • for is declaration of the loop
    • start statement is what will run at the start of the loop
    • exit condition is what will stop the loop if it returns false
    • increment statement will be run every time the code block finishes running
    • ; is the separator between each condition / statement and is necessary

    Run this code in developer console:

    for(Integer i = 0; i < 10; i++){
      System.debug('Hello '+ i);
    }

    Let' s break down this code. Here's what happened:

    1. Make an integer i and assign it the value of 0
    2. Is i smaller than 10? if it's smaller than 10, run the code block
    3. After code block is run, increment the value of i by 1.
    4. The statements 2, 3 and 4 run until the i is equal to 10.

    Here's how for loop works in terms of Coffee making. You're weighing your coffee and the first scoop had only 50 grams of coffee out of the 100 grams. A for loop in coffee terms would be:

    for (spoon = 0 grams; total_coffee < 100 grams; add another spoon){
      spoon.add(50 grams);
      weight.check();
    } 

    We start with 0 grams in our spoon, and add 50 grams of coffee with each spoon. Our exit condition is to ensure total coffee in our cup doesn't exceed 100 grams, so we keep adding coffee in 50 gram spoons until our exit condition is met.

    Looping over a List or Set

    To make this loop, we need two things:

    • A list or set to iterate over
    • A variable that will hold values temporarily

    The syntax is:

    for (dataType variableName: list_or_set){
        //code block
    }
    • for is the declaration of the loop
    • dataType is the data type of the variable
    • variableName is the variable that will temporarily hold the value
    • list_or_set is an existing list or set

    Run this code in developer console:

    List<String> names=new List<String>();
    
    names.add('Name 1');
    names.add('Name 2');
    names.add('Name 3');
    names.add('Name 4');
    
    for(String iterator: names){
      System.debug(iterator);
    } 

    Let's break down this code. Here's what happened:

    1. Make a list and add values to it.
    2. The for loop starts
    3. We have a variable named iterator that is of type string, which confirms to the data type of the list names which is a List of Strings
    4. Assign the first value of names to iterator and run the code block
    5. After the code block has run, assign the next value in names to iterator and run the code block
    6. If the list names have run out of values, exit the loop.

    A For loop can also be nested inside the for loop.

    Here's an example

    List<String> names=new List<String>();
    names.add('Name 1');
    names.add('Name 2');
    names.add('Name 3');
    names.add('Name 4');
    
    List<String> surnames=new List<String>();
    surnames.add('Name 3');
    surnames.add('Name 1');
    surnames.add('Name 4');
    surnames.add('Name 2');
    
    for (String outerLoop: names) {
      for (String innerLoop: surnames) {
        if (outerLoop==innerLoop) {
          System.debug(outerLoop + ' ' +innerLoop); //To add a space between both values
        }
      }
    }

    Let's break down this code:

    1. Make 2 lists and assign values to them
    2. outerLoop gets assigned the value Name 1 from names list.
    3. innerLoop gets assigned the value Name 3 from surnames list.
    4. If condition checks the value is not true
    5. innerLoop now gets assigned the value Name 1 from surnames list.
    6. If condition is true and it outputs "Name 1 Name 1" in the log innerLoop now gets assigned the value Name 4 and then Name 2.
    7. outerLoop now gets assigned Name 2 from names list.
    8. innerLoop gets assigned Name 3 from surnames list

    The outerloop will run once, then let it's inner loop finish completely before jumping on to the next value. The iterator variable in the for loop needs to match the data type of the list or set iterating over because every value in the said list or set will be assigned to the iterator variable one by one, and will throw an error if there's a mismatch of data types.

    Lists vs Maps

    Now that we know how for loops work, let's understand the difference between lists and maps.

    Take this example: I have a list of 100 emails with names. I have another list of 100 emails with phone numbers. I need to make a new list with Names, Phone Numbers and Emails.

    The following code is for demonstration purposes only

    Using a list and nested for loop the code would look something like this:

    List<String>  emailList = //list with email and name
    List<String> phoneList = //list with email and phone
    List<String> finalList = new List<String> ();
    
    for(String emailLooper: emailList) {
    	for (String phoneLooper: phoneList) {
    		if (emailLooper.email == phoneLooper.email){
    			finalList.add(emailLooper.email + emailLooper.name + phoneLooper.phone);
    		}
    
    	}
    }

    This loop takes the first email from emailList and matches it against every email in phoneList and when it finds a match it assigns it to the final list and keeps going on. This means, 100 emails * 100 numbers, which comes up to 10,000 runs.

    Using a map would look something like this:

    Map<String, String> emailList = //list with email and name
    Map<String, String> phoneList = //list with email and phone
    
    List<String>  finalList = new List<String> ();
    
    //Make empty variables
    String emailFromEmailList;
    String emailFromPhoneList;
    
    for (String emailLooper: emailList.keyset()){ // .keyset() returns all the values in maps
    	emailFromEmailList = emailList.get(emailLooper);
    	emailFromPhoneList = phoneList.get(emailLooper);
    
    	if (emailFromEmailList == emailFromPhoneList) {
    		finalList.add(emailList.name, emailList.email, phoneList.phone); //This is not how use maps, for demonstartion purposes only
    	}
    }

    This loop iterates over the email list 100 times and gets the correct value from the phone list by calling its key-value pair. Lists and Maps have their own time and place to use, but in this scenario using Maps resulted in 100x fewer iterations of the loop, resulting in less usage of computing power and resources.

    Summary

    • Lists, Sets and Maps can hold multiple values
    • A for loop runs until the exit condition is false
    Day 6: OOPs, Classes and Methods