Consider the following Apex class:
global class DealInfo {
public Integer amount {get;set;}
public String amountCurrency {get;set;}
}
Now, we are in a situation where we need to sort a list of objects from the preceding class. Let's run the following anonymous code:
DealInfo o = new DealInfo(); o.amount = 200; o.amountCurrency = 'USD'; DealInfo o1 = new DealInfo(); o1.amount = 100; o1.amountCurrency = 'USD'; List<DealInfo> lstDeals = new List<DealInfo>{o,o1}; lstDeals.sort();
If you are expecting the list to get sorted, then it's not so. We get the following error from Apex:
System.ListException: One or more of the items in this list is not Comparable.
Salesforce already supports sorting primitive datatypes. However, to support the sorting capabilities for objects, such as wrapper classes or lists, we need to implement the Comparable
interface provided by Apex. The Comparable
interface forces the class to implement the following method:
global Integer compareTo(Object compareTo) { // Your comparison logic here }
The compareTo
method returns the integer value using the following rules:
0
means that both the records are the same>0
means that the current record is greater than the record to be compared<0
means that the current record is smaller than the record to be comparedSo, a developer came up with the following code:
global class DealInfo implements Comparable { public Integer amount {get;set;} public String amountCurrency {get;set;} //Implement "compareTo" method from Comparable interface global Integer compareTo(Object objToCompare) { DealInfo obj = (DealInfo) objToCompare ; //compare two objects if(this == obj){ return 0; } return this.amount - obj.amount; } }
The preceding code is not correct because to compare two objects, the ==
operator is used in the code snippet. This operator only compares the reference (address) of the objects and does not consider their actual content. So, we should use the equals
method to compare objects in Apex.
After this discussion, an implementation of the compareTo
method should look something like the following code snippet:
global class DealInfo implements Comparable { public integer amount {get;set;} public String amountCurrency {get;set;} //Implement "compareTo" method from Comparable interface global Integer compareTo(Object objToCompare) { DealInfo obj = (DealInfo) objToCompare ; //Check if both objects are equal if(this.equals(obj)){ return 0; } //Which object is greater than other return this.amount - obj.amount; } }
There may be many developers out there who knowingly or unknowingly ignore the importance of the equals()
and hashcode()
methods while implementing the Comparable
interface.
In the preceding code, we should use the equals()
method. The preceding code is not yet correct.
To understand the problem in detail, execute the following anonymous code:
DealInfo o = new DealInfo(); o.amount = 200; o.amountCurrency = 'USD'; DealInfo o1 = new DealInfo(); o1.amount = 200; o1.amountCurrency = 'USD'; System.assertEquals(o, o1);
In the preceding code, when we try to check whether both the objects are equal using the System.assertEquals()
method, we will receive an error saying that the objects are not the same as expected:
Line: 9, Column: 1 System.AssertException: Assertion Failed: Expected: DealInfo:[amount=200, amountCurrency=USD], Actual: DealInfo:[amount=200, amountCurrency=USD]
Both the records are exactly the same but why does the assertion still fail? This is because Apex doesn't know how to compare the two objects, and it's the developer's job to implement the equals method to instruct Salesforce to determine equality between two objects. Along with equals()
, we should implement the hashcode()
method as well. Hashcodes are used by the platform to index the object location in heap memory. Another reason why we should implement hashcode()
is that Salesforce supports Apex classes as keys in maps and sets. If we do not implement hashcode()
, then the same object would not be retrieved if it's used as a key in a map or a set.
As a best practice, the following guidelines should be followed while overriding the equals() method:
= = =
) available in Apex.All the available operators in Apex are listed in the official documentation page at https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/langCon_apex_expressions_operators_understanding.htm.
General contracts between the and equals()
hashcode() and equals() method in Apex are as follows:
Considering all the points that we discussed, the following code snippet is the correct implementation of the equals()
, hashcode()
, and compareTo()
methods:
global class DealInfo implements Comparable { public Integer amount {get;set;} public String amountCurrency {get;set;} //Implement "compareTo" method from Comparable interface global Integer compareTo(Object objToCompare) { DealInfo obj = (DealInfo) objToCompare ; if(this.equals(obj)){ return 0; } return this.amount - obj.amount; } //Override equals() method public Boolean equals(Object o) { //Rule 1 if (this === o) { return true; } //Rule 2 if ( (o == null) || !(o instanceof DealInfo) ) { return false; } //Rule 3 DealInfo obj = (DealInfo)o; if(obj.amount == this.amount && obj.amountCurrency == this.amountCurrency) { return true; } return false; } //Override hashcode() method public Integer hashCode() { return this.amount + this.amountCurrency.hashcode(); } }