Given the following Apex class, which implements equals()
and hashCode()
as per the Using Custom Types in Map Keys and Sets documentation:
public class Foo
{
private Integer x;
public Foo(Integer x)
{
this.x = x;
}
public Boolean equals(Object obj)
{
System.debug('equals');
if(obj instanceof Foo)
{
Foo f = (Foo)obj;
return x == f.x;
}
return false;
}
public Integer hashCode()
{
System.debug('hashCode');
return x;
}
}
When running the following in the Execute Anonymous window:
Foo f1 = new Foo(1);
Foo f2 = new Foo(2);
System.debug('Map');
Map<Foo, Boolean> m = new Map<Foo, Boolean>();
System.debug('put');
m.put(f1, true);
m.put(f2, true);
System.debug('get');
System.debug(m.get(f1));
System.debug(m.get(f2));
System.debug('Set');
Set<Foo> s = new Set<Foo>();
System.debug('add');
s.add(f1);
s.add(f2);
System.debug('contains');
System.debug(s.contains(f1));
System.debug(s.contains(f2));
I get the following output in the log:
10:20:47.109 (109419811) USER_DEBUG [4]|DEBUG|Map
10:20:47.113 (113647288) USER_DEBUG [8]|DEBUG|put
10:20:47.197 (197941404) USER_DEBUG [12]|DEBUG|equals
10:20:47.202 (202365558) USER_DEBUG [12]|DEBUG|get
10:20:47.220 (220325097) USER_DEBUG [12]|DEBUG|equals
10:20:47.245 (245972058) USER_DEBUG [12]|DEBUG|equals
10:20:47.250 (250500074) USER_DEBUG [13]|DEBUG TRUE
10:20:47.267 (267329766) USER_DEBUG [12]|DEBUG|equals
10:20:47.271 (271834178) USER_DEBUG [14]|DEBUG|true
10:20:47.271 (271888873) USER_DEBUG [16]|DEBUG|Set
10:20:47.275 (275988792) USER_DEBUG [20]|DEBUG|add
10:20:47.296 (296012585) USER_DEBUG [12]|DEBUG|equals
10:20:47.301 (301160512) USER_DEBUG [24]|DEBUG|contains
10:20:47.324 (324164712) USER_DEBUG [12]|DEBUG|equals
10:20:47.351 (351133028) USER_DEBUG [12]|DEBUG|equals
10:20:47.357 (357265136) USER_DEBUG [25]|DEBUG|true
10:20:47.382 (382010122) USER_DEBUG [12]|DEBUG|equals
10:20:47.388 (388259431) USER_DEBUG [26]|DEBUG|true
As you can see, hashCode()
is not called once, instead equals()
is called against all existing members of the collection (ok, that's maybe not clear from the log, but increase the volumes and it's very clear, it's also take forever when the Map gets above about 10 elements) which goes contrary to this pair of statements in the Map documentation as well as the standard Java implementation.
Unlike Java, Apex developers do not need to reference the algorithm that is used to implement a map in their declarations (for example, HashMap or TreeMap). Apex uses a hash structure for all maps.
Uniqueness of map keys of user-defined types is determined by the
equals
andhashCode
methods, which you provide in your classes. Uniqueness of keys of all other non-primitive types, such as sObject keys, is determined by comparing the objects’ field values.
I'm sure my implementation of hashCode()
is 'right' (other than being a terrible means of creating a hash) since the following produces the correct output:
Object o = new Foo(1);
System.debug(o.hashCode());
System.debug(System.hashCode(o));
Log output:
10:41:11.659 (659323616) USER_DEBUG [25]|DEBUG|hashCode
10:41:11.659 (659395719) USER_DEBUG [2]|DEBUG|1
10:41:11.745 (745368812) USER_DEBUG [25]|DEBUG|hashCode
10:41:11.745 (745439365) USER_DEBUG [3]|DEBUG|1
I am seeing this issue across multiple Orgs on multiple instances. Is this a bug with the platform? Has this worked correctly in previous releases?