I've written a custom class that I'm using to store label/value pairs for use in lightning component picklists. I've got quite a complex dependency requirement, so I thought I'd use the "retainAll" method on the apex Set to get the intersections.
This is the class:
public with sharing class ListOption implements Comparable {
public Id value { get; set; }
public String label { get; set; }
public ListOption (Id value, String label) {
this.value = value;
this.label = label;
}
public Boolean equals(Object obj) {
if (obj == null || !(obj instanceof ListOption)) {
return false;
}
ListOption lo = (ListOption) obj;
return this.value == lo.value && this.label == lo.label;
}
public Integer hashCode() {
return (31 * String.valueOf(this.value).hashCode())
^ this.label.hashCode();
}
public Integer compareTo(Object obj) {
ListOption lo = (ListOption) obj;
return this.label.compareTo(lo.label);
}
}
Now, this is the code I'm testing with, and seeing the strange results:
ListOption lo1 = new ListOption('a0C41000000NUU8EAO', '2017 Fall');
ListOption lo2 = new ListOption('a0C41000000NUU8EAO', '2017 Fall');
System.debug(lo1.equals(lo2));
System.debug(lo1.hashCode());
System.debug(lo2.hashCode());
Set<ListOption> s1 = new Set<ListOption>();
s1.add(lo1);
Set<ListOption> s2 = new Set<ListOption>();
s2.add(lo2);
System.debug('Contains: ' + s1.contains(lo2));
System.debug('Contains All: ' + s1.containsAll(s2));
s1.retainAll(s2);
System.debug(s1);
There are a variety of debug statements in here, and they all behave as expected except for the last one.
The two objects are equals, the "contains" and "containsAll" calls both return true.
But the "retainAll" is resulting in my set being emptied. This is unexpected, as the two values in the sets are equal, I would expect the set to retain the value it already contains.
Is my expectation incorrect?
In response to a comment below, here's another angle. I've got a method that I'm using to find the intersection between a list of sets, the method is here:
public static List<ListOption> findIntersection(List<Set<ListOption>> sets) {
if (sets.isEmpty()) {
return new List<ListOption>();
}
Integer counter = 0;
Set<ListOption> temp;
Set<ListOption> commons = sets[0];
Iterator<Set<ListOption>> i = sets.iterator();
List<ListOption> toReturn;
System.debug('First Commons');
System.debug(JSON.serialize(commons));
while (i.hasNext()) {
temp = i.next();
System.debug(JSON.serialize(temp));
commons.retainAll(temp);
System.debug('Pruned Commons');
System.debug(JSON.serialize(commons));
}
toReturn = new List<ListOption> (commons);
toReturn.sort();
return toReturn;
}
Here's my anonymous block calling the method:
ListOption lo1 = new ListOption('a0C41000000NUU8EAO', '2017 Fall');
ListOption lo2 = new ListOption('a0C41000000NUU8EAO', '2017 Fall');
ListOption lo3 = new ListOption('a0C41000000OKwWEAW', 'Fall 2016');
Set<ListOption> s1 = new Set<ListOption>();
s1.add(lo1);
s1.add(lo3);
Set<ListOption> s2 = new Set<ListOption>();
s2.add(lo2);
System.debug(DependencySelectionController.findIntersection(new List<Set<ListOption>> { s1, s2 }));
The output is showing my set being emptied:
13:34:48.6 (12120220)|USER_DEBUG|[156]|DEBUG|First Commons
13:34:48.6 (12798334)|USER_DEBUG|[157]|DEBUG|[{"value":"a0C41000000NUU8EAO","label":"2017 Fall"},{"value":"a0C41000000OKwWEAW","label":"Fall 2016"}]
13:34:48.6 (13250587)|USER_DEBUG|[161]|DEBUG|[{"value":"a0C41000000NUU8EAO","label":"2017 Fall"},{"value":"a0C41000000OKwWEAW","label":"Fall 2016"}]
13:34:48.6 (14182499)|USER_DEBUG|[163]|DEBUG|Pruned Commons
13:34:48.6 (14908699)|USER_DEBUG|[164]|DEBUG|[{"value":"a0C41000000NUU8EAO","label":"2017 Fall"},{"value":"a0C41000000OKwWEAW","label":"Fall 2016"}]
13:34:48.6 (15184228)|USER_DEBUG|[161]|DEBUG|[{"value":"a0C41000000NUU8EAO","label":"2017 Fall"}]
13:34:48.6 (15296611)|USER_DEBUG|[163]|DEBUG|Pruned Commons
13:34:48.6 (15375673)|USER_DEBUG|[164]|DEBUG|[]
13:34:48.6 (15484370)|SYSTEM_MODE_EXIT|false
13:34:48.6 (15521569)|USER_DEBUG|[14]|DEBUG|()
s1
did not get emptied.