==
, ===
, equals
and equalsIgnoreCase
are all slightly different. I'll go over all of these in more detail. tl;dr at bottom.
===
You're not allowed to directly use this on String variables, but cast to an Object, and you'll get reference comparisons. This works because Apex borrows the idea of Java's "string pool", where a duplicate string from the pool just returns the reference. This is a heap memory trick to reduce memory usage in a program. Since strings are immutable in memory (any mutation causes a new string to be created in the pool), this is perfectly safe. It's also the fastest of the comparison options we have, but is strictly case-sensitive and locale-agnostic.
Object v1 = 'hello world';
Object v2 = 'hello world';
System.debug(v1 === v2); // true
equals
This method first checks the length of the two strings, and if they match, proceeds to check each character one at a time until a mismatch or the strings are fully compared. For equal strings, this is just about as fast as ===
. Even on a non-match in a very large string (say, 1,000,000 characters), this is still only about twice as slow as ===
. It's a good choice for case-sensitive, locale-agnostic comparisons. Just be aware that the variable equals
is called from must not be null, or you'll throw an exception. If you're not sure, check for null first, or use the Safe Navigation Operator (?.
).
String v1 = 'hello world';
String v2 = 'hello world';
System.debug(v1.equals(v2)); // true
System.debug(v1?.equals(v2) == true); // true, use if you need null safety
equalsIgnoreCase
We use this when we want case-insensitive, locale-agnostic string comparisons. It's roughly the same speed as v1.toLowerCase().equals(v2.toLowerCase())
(or toUpperCase
). That is, it's far slower than either ===
or equals
, but much, much faster than ==
in some scenarios. Note that because of a lack of locale awareness, it can produce false positives or false negatives. This is only possible in a handful of languages where certain characters between cases differ. It's a concept that I find challenging to explain, as I don't have any concrete examples, nor am I a linguistics expert. Just make sure you test some expected inputs to see if you get any weird results.
String v1 = 'hello world';
String v2 = 'HELLO WORLD';
System.debug(v1.equalsIgnoreCase(v2)); // true
System.debug(v1?.equalsIgnoreCase(v2) == true); // true, use if you need null
==
The default operator, which in the documentation is merely described as case-insensitive matching, is actually a locale-aware case-insensitive comparison. It is roughly the equivalent of calling v1.toLowerCase( UserInfo.getLocale() ).equals( v2.toLowerCase( UserInfo.getLocale() ) )
. Enabling locale-awareness comes at considerable cost in CPU time.
If you need locale-aware, case-insensitive matching, you'd be better off converting to a common casing and using ===
:
String userLocale = UserInfo.getLocale();
Boolean isMatch = (Object)(v1?.toLowerCase(userLocale)) === (Object)(v2?.toLowerCase(userLocale));
This should be as accurate as ==
is, but provide much better performance with large strings.
tl;dr
We have four ways to check equality: two case-sensitive models, one locale-agnostic case-insensitive model, and one locale-aware case-sensitive model. ==
is the most technically accurate comparison, but has the worst potential performance; it performs better than equalsIgnoreCase
on small strings, but suffers on large strings. equalsIgnoreCase
is less technically accurate, but has far better performance compared to ==
if you need to deal with strings of large sizes. If you're not sure which one you need, just stick with ==
. It'll probably be okay.
If you start to notice high CPU usage, consider if locale-awareness is worth it. If not, you can go with equalsIgnoreCase
or (Object)toLowerCase === (Object)toLowerCase
, and if so, you can switch to (Object)toLowerCase(userLocale) === (Object)toLowerCase(userLocale)
(see examples, above).
The reason why ==
beats .equalsIgnoreCase
on small strings is that calling methods involves the using the call stack, which has a very small, but non-zero, CPU penalty, but once the strings get large enough, ==
performance problems will become more apparent.