[SalesForce] How to compare two sObjects from 2 List()

I'm in need of a way to see if changes were made to a reference of an object, and compare to the original values. This is independent of the trigger context variables such as oldMap + newMap.

Is there a way to clone a List of Leads, make changes (if needed) and compare whether changes were made? Ideally, I'd like to compare the entire instance of the object and not evaluate each field. I suspect there is the issue of Pass By Reference vs Pass By Value.

Here's my sample code which doesn't yield my expected results. I'd expect all 3 systemdebugs to evaluate to TRUE before the change, then all 3 evaluate to FALSE after the change.

List<Lead> l1 = [select Id, Phone from Lead where Id = '00QK0000006wwpDMAQ'];
List<Lead> l2 = l1.clone();

system.debug('/***Before Update to List2 Records***/');
system.debug('');
system.debug('standard compare (==): ' + Boolean.valueOf(l1==l2));
system.debug('memory compare: (===): ' + Boolean.valueOf(l1===l2));
system.debug('listMethod.equals(list2) compare: ' + l1.equals(l2));


//modify phone number of record in list 2 (clone of list 1);
for(Lead l: l2){

    l.phone = '111-111-1111'; //number is certainly changed from original value

}

system.debug('');
system.debug('/***After Update to List2 Records***/');
system.debug('');
system.debug('standard compare (==): ' + Boolean.valueOf(l1==l2));
system.debug('memory compare: (===): ' + Boolean.valueOf(l1===l2));
system.debug('listMethod.equals(list2) compare: ' + l1.equals(l2));

Best Answer

The comparison operators don't work the way you think they do, not even across all objects. Let's focus on SObject and SObject[] types for this question, since that's your question.

== acts as a deep comparison, comparing every field in an SObject with the same field in the other SObject. This basically means that it's a byte-wise operation, and the results are only true if they match the same number of fields with the same values. The list version of == works the same way. The number of indices must match, and each record in each list must satisfy == as described above. This means that your "before" comparison should yield true for the first and third elements (equals() is synonymous with ==), and false for the first and third tests in your "after" comparison (except, you have a bug, details later...).

===, however, is a shallow comparison. It simply compares the memory address of both operands and returns true if they are equal, false otherwise. This means that both references must point to the same object in the heap to return a true value. clone() returns a new, shallow copy of the original list. This means that === returns false for l1 === l2, because they're not the same object, but l1[0] === l2[0] returns true, because they are the same object.

Of course, I pointed out that you have a flaw in your logic. When you assigned a value to l2[0].Phone, you also updated l1[0].Phone, because they're both the same object in memory! In order to gain a truly separate copy of the records, you have to actually use List.deepClone in order to have two unique lead records in memory. This actually leads l1 == l2 to still be true in your "after" comparison.

To get closer to what you expect, you have to change your code:

Lead[] l1 = [SELECT ... FROM Lead WHERE Id = ...];
Lead[] l2 = l1.deepClone(true);
System.assertEquals(true, l1 == l2);
System.assertEquals(false, l1 === l2);
System.assertEquals(true, l1.equals(l2));
l2[0].Phone = '555-4240';
System.assertEquals(false, l1 == l2);
System.assertEquals(false, l1 === l2);
System.assertEquals(false, l1.equals(l2));

To see how === could return true, you'd have to assign the same reference to two variables:

Lead[] l1 = [SELECT ...];
Lead[] l2 = l1;
System.assertEquals(true, l1 === l2);

This comparison may seem pointless at first glance, and indeed, it's pretty rare when you actually want to know if two objects are actually the same physical object in memory, but this operator is useful for certain classes of algorithms where the only thing you might know is if memory location A is the same as memory location B. I don't think I have ever used this comparison in production code (===, that is), but this question illustrates one such use.

To illustrate other places where === can trip you up, consider the following code:

Object a = 5, b = 5;
System.assertEquals(false, a === b);
b = a;
System.assertEquals(true, a === b);

If this doesn't make sense to you, remember that numbers are basically syntactic sugar for something like Integer.valueOf(5)-- they are technically classes, and as such, the first line returns false because they don't point to the same memory reference, while after assigning a to b, they do point to the same memory reference. Fortunately, most primitives are immutable, and we don't have any way to traverse references like we do in C/C++, so it's not possible to change a by changing b, for example.

So, if you really want to compare changes between two lists, first, make sure that you're really working with two unique records (=== should be false), and then you can find the values that have changed. Here's some things you can do:

Set<Lead> sl1 = new Set<Lead>(l1);
Set<Lead> sl2 = new Set<Lead>(l2);
sl2.removeAll(sl1);  // Retains only records that are different
Lead[] toUpdate = new List<Lead>(sl2);

Or, you can iterate over each item:

Lead[] toUpdate = new Lead[0];
for(Integer index = 0, size = l1.size(); index < size; index++) {
    if(l1[index] != l2[index]) {
        toUpdate.add(l2[index]);
    }
}

And, of course, as you've observed, you can check to see if any changes have occurred by way of l1 == l2 or l1.equals(l2).

Please note that the == operator can be very expensive, as much as several hundred milliseconds per use on records that hundreds of fields.

Related Topic