Maps and Sets have different internal implementations depending on the debugging level of your TraceFlag, if any. This is for debugging purposes. The highest, FINEST, behaves differently than the lower levels of debugging. Let's look at your code again, using execute anonymous:
class IAccounts {}
Map<Object, Object> m = new Map<Object, Object>();
String s = 's';
m.put(IAccounts.class, s); // any type here does the trick
System.debug(LoggingLevel.ERROR, m.containskey(IAccounts.class));
System.debug(LoggingLevel.ERROR, m.get(IAccounts.class));
In your Developer Console, go to Debug > Change Log Levels > Add/Change, and set Apex to "ERROR". You'll see the output true
, s
. Now, go back and change this to "FINEST", and repeat the experiment. You'll get false
, null
.
This is a known "bug" of how maps and sets work, and it won't be fixed. The different implementations at the highest level is meant to ensure that the map/set can be serialized correctly. I don't know the internals, but it causes problems for certain types of keys, like keys which decide to change their hashcode after being put into the map.
This is also why you'd observe different effects in different sandboxes, even on the same patch level. Your choice of what level to debug at is stored in each org independently, so simply changing the flags are enough to influence unit tests and debugging sessions in general.
Edit:
Note that this has to do with how the implementations work. The lower levels use a combination of equals/hashCode, as described in Using Custom Types in Map Keys and Sets, but switch to a ===
based approach in FINEST mode.
Example code:
class iaccounts {}
object i = iaccounts.class;
// All these assertions pass as of 59.0
Assert.areEqual(i.hashCode(), ((Object)iaccounts.class).hashCode());
Assert.isTrue(i.equals(iaccounts.class));
Assert.isFalse(i === iaccounts.class);
Assert.isFalse(iaccounts.class === iaccounts.class);