Set.retainAll() empties set with custom classes

Consider this minimal example:

class Test {
    private String val;
    public Test(String str) {
        this.val = str.toLowerCase();
    }
    public Boolean equals(Object obj) {
        if (obj instanceOf Test) {
            return val == ((Test) obj).val;
        }
        return false;
    }
    public Integer hashCode() {
        return val.hashCode();
    }
}

Set<Test> a = new Set<Test>();
a.add(new Test('abc'));

Set<Test> b = new Set<Test>();
b.add(new Test('abc'));

b.retainAll(a);
System.debug(b);

Set b is emptied. This is similar to existing question, Apex Set retainAll() with custom class.

It appears like the retainsAll ignores equals and hashCode and instead works with references, because the snippet below will work as expected:

Test abc = new Test('abc');
Set<Test> a = new Set<Test>();
Set<Test> b = new Set<Test>();
a.add(abc);
b.add(abc);
b.retainAll(a);
System.debug(b);

If we create separate references, like Test bar = new Test('abc') and Test foo = new Test('abc'), bar.equals(foo), foo.equals(bar) and bar.hashCode() == foo.hashCode(), but the set b is cleared.

Answer

After much time wasting, Salesforce has decided to agree that this is a bug: https://success.salesforce.com/issues_view?title=the-result-of-set-sobject-retainall-set-sobject-are-inconsistent-between-apexcode-log-level-is-finest-and-it-is-finer-or-lower&Id=a1p3A000001HlAf

Attribution
Source : Link , Question Author : ipavlic , Answer Author : ipavlic

Leave a Comment