Java char comparison looks deceptively simple — until you paste two snippets side by side and they behave differently from what you expect. Whether you need to compare two characters in Java using the == operator, the Character.compare() static method, or equals() on a Character wrapper object, each approach has distinct semantics. This guide covers every method for character compare Java scenarios — from basic java equals char checks to Unicode surrogate pairs — so you always pick the right tool and avoid the bugs that lurk in the wrong one.

What Is Character Comparison in Java?

Java char: Two Type Representations primitive char 16-bit unsigned integer 0x0000 – 0xFFFF Lives on: stack / field inline Overhead: 0 bytes char c = 'A'; // 65 Character (wrapper) Object wrapping one char java.lang.Character Lives on: heap (reference) Overhead: ~16 bytes header Character c = 'A'; autobox / unbox
The primitive char type vs the Character wrapper class — two very different memory models.

In Java, a character is stored as a 16-bit unsigned integer whose value corresponds to a Unicode code point. The primitive type char can hold values from 0 to 65535 (Unicode Basic Multilingual Plane). Because it is a numeric value under the hood, you can compare two char primitives using all the same relational operators you would use on int: ==, !=, <, >, <=, and >=.

Java also provides a wrapper class, java.lang.Character, which boxes a primitive char into an object. This is where comparisons start getting tricky. The same gotcha that bites developers with String comparison — using == on objects instead of equals() — applies here too. If you have already explored Java string comparison, the rules for Character objects will feel familiar.

There are four core methods for java char comparison:

  • == — safe for primitive char; unreliable for Character objects
  • Character.equals() — value equality for wrapper objects
  • Character.compare(ch1, ch2) — static method, returns negative / zero / positive
  • Character.compareTo(ch2) — instance method, same numeric result as compare()

Understanding when to use which method is the entire point of this guide. Let's start with the fundamental distinction that drives every other decision.

Primitive char vs Character Wrapper Class

Memory Layout: primitive char vs Character Object STACK char a 0x0041 = 'A' (65) char b 0x005A = 'Z' (90) ref chObj 0xFF3C20 2 bytes each (char) 4 bytes (ref pointer) HEAP Character object mark word (8 bytes) class pointer (4-8 bytes) value: 0x004D = 'M' (char field, 2 bytes) Total: ~16 bytes overhead
Stack vs heap: a primitive char stores its value directly; a Character object adds header overhead and an extra indirection via a heap reference.

Java's type system draws a sharp line between primitive types and their object counterparts. For characters:

  • Primitive char — lives on the stack (or inline in an object's fields), holds exactly 16 bits, and represents a single UTF-16 code unit. No object overhead.
  • Character wrapper — a heap object that wraps one char value. It adds object header overhead (~16 bytes on a 64-bit JVM) and reference semantics. The JVM maintains a cache of Character instances for code points 0 through 127, mirroring the behavior of Integer caching.

You typically work with primitive char when iterating over a String via charAt() or toCharArray(), parsing tokens, or performing fast single-character checks. You encounter Character wrapper objects when storing characters in collections (List<Character>, Map<Character, Integer>), using autoboxing in generics, or calling APIs that return Character.

The core rule that governs how to compare char Java correctly: use == for primitives; use equals() or Character.compare() for wrapper objects.

// Primitive — == is safe and idiomatic
char a = 'A';
char b = 'A';
System.out.println(a == b); // true — numeric value comparison

// Wrapper — == compares references, not values
Character chA = new Character('A'); // deprecated constructor, shown for clarity
Character chB = new Character('A');
System.out.println(chA == chB);      // false — different heap objects
System.out.println(chA.equals(chB)); // true — same wrapped value

Note: new Character(ch) is deprecated since Java 9. Use Character.valueOf(ch) instead, which leverages the internal cache.

Using == Operator for char Comparison

The == operator performs a numeric equality check when both operands are primitive char values. Since char is a 16-bit unsigned integer, a == b is literally comparing two integer code point values — exactly what you want for character compare Java in most cases.

char x = 'Z';
char y = 'Z';
System.out.println(x == y); // true

// Relational operators also work on primitives
char lo = 'a';
char hi = 'z';
System.out.println(lo < hi);  // true  (97 < 122)
System.out.println(lo > hi);  // false
System.out.println(lo != 'b'); // true

// Iterating a string — == on primitives is correct
String word = "hello";
int lCount = 0;
for (char c : word.toCharArray()) {
    if (c == 'l') lCount++;
}
System.out.println(lCount); // 2

Where == breaks down is when one or both operands are Character objects. In that case, == compares object references (memory addresses), not character values. Two separate Character objects wrapping the same letter are different objects in memory and will compare as unequal unless they happen to come from the JVM's internal cache.

// Autoboxed from a method call that returns Character
Character c1 = Character.valueOf('A'); // cached (0-127)
Character c2 = Character.valueOf('A'); // same cached instance
System.out.println(c1 == c2); // true — both reference the cache entry

// Explicitly new — NOT cached
Character c3 = new Character('A');
Character c4 = new Character('A');
System.out.println(c3 == c4); // false — different heap objects!
System.out.println(c3.equals(c4)); // true — always use equals() for wrappers

The cache behavior makes == appear to work for common ASCII characters when Character.valueOf() is used — but it is an implementation detail, not a contract. Relying on it produces bugs that are hard to reproduce. Always use equals() or Character.compare() when dealing with Character wrapper objects.

Understanding equals() for Character Objects

Character.equals(Object obj) performs a value comparison between two Character wrapper objects. It returns true if and only if obj is also a Character instance wrapping the same char value. This is the java equals char pattern you should default to whenever you have wrapper objects rather than primitives.

Character ch1 = Character.valueOf('M');
Character ch2 = Character.valueOf('M');
Character ch3 = Character.valueOf('N');

System.out.println(ch1.equals(ch2)); // true — same value
System.out.println(ch1.equals(ch3)); // false — different values
System.out.println(ch1.equals("M")); // false — different type (String, not Character)
System.out.println(ch1.equals(null)); // false — null-safe

A few things to know about Character.equals():

  • It is null-safe on the argument: passing null returns false rather than throwing a NullPointerException.
  • It is not null-safe on the receiver: calling null.equals(ch) throws NullPointerException. If the receiver might be null, use Objects.equals(ch1, ch2) from java.util.Objects.
  • Passing a String containing one character does not equal a Character — they are different types.
import java.util.Objects;

Character maybeNull = null;
Character other = 'X';

// Safe null handling
System.out.println(Objects.equals(maybeNull, other)); // false — no NPE
System.out.println(Objects.equals(maybeNull, null));  // true

// Unbox then compare — also valid when you know neither is null
char primitive = other; // auto-unboxing
System.out.println(primitive == 'X'); // true

Character.compare() Method Explained

ASCII Code Points: Digits, Uppercase & Lowercase Digits (48–57) '0' 48 '1' 49 '2' 50 '3' 51 '4' 52 '5' 53 '6' 54 '7' 55 '8' 56 '9' 57 Uppercase A–Z (65–90) 'A' 65 'B' 66 'C' 67 'D' 68 . . . 'X' 88 'Y' 89 'Z' 90 Lowercase a–z (97–122) — gap +32 from uppercase 'a' 97 'b' 98 'c' 99 'd' 100 . . . 'x' 120 'y' 121 'z' 122 +32 gap Key insight for case-insensitive comparison 'A'=65, 'a'=97 → difference is always 32 Character.toLowerCase() / Character.toUpperCase() uses this mapping internally
ASCII code points for digits (48–57), uppercase letters (65–90), and lowercase letters (97–122). The 32-point gap between 'A' and 'a' drives case conversion.

Character.compare(char x, char y) is a static utility method introduced in Java 7. It compares two primitive char values numerically and returns:

  • 0 if x == y
  • A negative integer if x < y (x has a lower code point)
  • A positive integer if x > y (x has a higher code point)

The exact non-zero return value is x - y (the difference in Unicode code points), but you should only rely on the sign, not the magnitude. This is the same contract as Comparable.compareTo().

// Equality
System.out.println(Character.compare('A', 'A')); // 0

// Ordering
System.out.println(Character.compare('A', 'Z')); // negative (65 - 90 = -25)
System.out.println(Character.compare('Z', 'A')); // positive (90 - 65 = 25)

// Sorting a char array using compare
Character[] letters = {'D', 'A', 'C', 'B'};
java.util.Arrays.sort(letters, Character::compare);
System.out.println(java.util.Arrays.toString(letters)); // [A, B, C, D]

// Using in a Comparator lambda
java.util.List<Character> list = new java.util.ArrayList<>(java.util.Arrays.asList('z', 'a', 'm'));
list.sort((c1, c2) -> Character.compare(c1, c2));
System.out.println(list); // [a, m, z]

Character.compare() is the preferred method when you need to sort characters or implement a Comparator. Unlike subtracting code points directly (x - y), it avoids any potential integer overflow issues — though in practice overflow cannot occur for char since the range is 0–65535 and the difference always fits in an int. Still, the static method communicates intent more clearly and is consistent with the rest of the Java API.

Comparison Methods at a Glance

Method Works on Returns Best for Common pitfall
== (primitive) Two char primitives boolean Fast equality / loop conditions None — correct and idiomatic for primitives
== (object) Two Character wrappers boolean Avoid entirely Compares references, not values; silently wrong above code point 127
equals() Character wrapper boolean Wrapper equality; null-safe arg NPE if receiver is null; use Objects.equals() instead
Character.compare() (static) Two char primitives int (neg/0/pos) Sorting, Comparator lambdas None — preferred over subtraction for clarity
Character.compareTo() (instance) Character wrapper int (neg/0/pos) TreeSet, TreeMap, natural ordering APIs NPE if either operand is null

Character.compareTo() Method Explained

Character.compareTo(Character anotherCharacter) is the instance method equivalent of Character.compare(). It implements the Comparable<Character> interface, enabling Character objects to be compared, sorted in TreeSet / TreeMap, and used with the Java Collections framework's natural ordering.

Character ch1 = Character.valueOf('C');
Character ch2 = Character.valueOf('B');
Character ch3 = Character.valueOf('C');

System.out.println(ch1.compareTo(ch2)); // positive (C > B; code points 67 - 66 = 1)
System.out.println(ch2.compareTo(ch1)); // negative (B < C; 66 - 67 = -1)
System.out.println(ch1.compareTo(ch3)); // 0 (equal)

// Natural ordering in a TreeSet
java.util.TreeSet<Character> set = new java.util.TreeSet<>();
set.add('G');
set.add('A');
set.add('D');
System.out.println(set); // [A, D, G] — sorted by Unicode code point

The relationship between the two methods:

  • ch1.compareTo(ch2) is equivalent to Character.compare(ch1, ch2)
  • Both return the same value: ch1 - ch2 (as Unicode code point difference)
  • compareTo() requires a Character object receiver; compare() takes two primitives

See the sister article on String compareTo in Java for a deeper look at how the Comparable interface works across Java types, and how compareTo() integrates with sorting APIs.

Case-Insensitive Character Comparison

The Java char type is case-sensitive by default: the code point for 'A' is 65 and for 'a' is 97, so a direct == returns false. For case-insensitive character compare Java, you have three options:

  • Normalize both characters to the same case before comparing
  • Use Character.toLowerCase() or Character.toUpperCase()
  • Compare Unicode code points after applying case folding
char ch1 = 'A';
char ch2 = 'a';

// Option 1: normalize to lower case
boolean equalIgnoreCase = Character.toLowerCase(ch1) == Character.toLowerCase(ch2);
System.out.println(equalIgnoreCase); // true

// Option 2: normalize to upper case
boolean equalUpper = Character.toUpperCase(ch1) == Character.toUpperCase(ch2);
System.out.println(equalUpper); // true

// Option 3: use Character wrapper equals() after boxing
Character boxed1 = ch1;
Character boxed2 = ch2;
boolean wrappedEqual = Character.toLowerCase(boxed1) == Character.toLowerCase(boxed2);
System.out.println(wrappedEqual); // true

Be aware that Character.toLowerCase() and Character.toUpperCase() are locale-independent. They use the Unicode standard case mappings. For most Latin-script characters this is fine. For locale-sensitive scenarios — such as Turkish, where the uppercase of 'i' is 'İ' (dotted capital I) rather than 'I' — you need to involve a Locale or work at the String level:

// Locale-aware case-insensitive comparison at the String level
String s1 = String.valueOf('i');
String s2 = String.valueOf('I');

// Fails for Turkish locale if using the default:
boolean safeTurkish = s1.equalsIgnoreCase(s2); // true for most locales

// Locale-explicit approach:
java.util.Locale turkey = new java.util.Locale("tr", "TR");
boolean turkishSafe = s1.toLowerCase(turkey).equals(s2.toLowerCase(turkey));
// For Turkish: 'i' lowercases to 'i', 'I' lowercases to 'ı' — NOT equal!
System.out.println(turkishSafe); // false (correct Turkish behavior)

For the vast majority of English-language applications, Character.toLowerCase(ch1) == Character.toLowerCase(ch2) is both correct and efficient. Just be aware of the locale edge case if your app targets Turkish or other languages with unusual casing rules.

Unicode, Code Points & Surrogate Pairs

UTF-16 Surrogate Pair: How Two chars Encode One Code Point Example: U+1F600 (Grinning Face emoji) Code Point U+1F600 (0x1F600) encode as UTF-16 0x1F600 − 0x10000 = 0xF600 → binary: 0000 1111 0110 0000 0000 High Surrogate char = '\uD83D' range: U+D800 – U+DBFF Low Surrogate char = '\uDE00' range: U+DC00 – U+DFFF Character.toCodePoint(high, low) Reconstructed Code Point int cp = 0x1F600 (128512) String len=2 (two chars), but codePointCount=1 (one character)
A supplementary Unicode character (emoji, historic scripts) occupies two consecutive char values — a high and low surrogate — in Java's UTF-16 encoding.

Java's char is a 16-bit unsigned integer representing a single UTF-16 code unit. The Unicode standard defines code points from U+0000 to U+10FFFF. Characters in the Basic Multilingual Plane (BMP) — code points U+0000 through U+FFFF — fit in a single char. Characters outside the BMP (supplementary characters, including many emoji, historic scripts, and some CJK extension characters) require two char values called a surrogate pair. See the official Oracle Character class documentation for the complete API contract.

A surrogate pair consists of:

  • A high surrogate: code points U+D800 to U+DBFF
  • A low surrogate: code points U+DC00 to U+DFFF
// Detecting a surrogate pair
char high = '\uD83D'; // high surrogate (part of U+1F600, 😀)
char low  = '\uDE00'; // low surrogate

System.out.println(Character.isHighSurrogate(high)); // true
System.out.println(Character.isLowSurrogate(low));   // true
System.out.println(Character.isSurrogatePair(high, low)); // true

// Convert surrogate pair to full code point
int codePoint = Character.toCodePoint(high, low);
System.out.println(Integer.toHexString(codePoint)); // 1f600

// Comparing supplementary characters safely — use code points
String emoji1 = "😀"; // 😀
String emoji2 = "😀";
// char-by-char == on surrogates works for same emoji, but use codePointAt() for clarity
int cp1 = emoji1.codePointAt(0);
int cp2 = emoji2.codePointAt(0);
System.out.println(cp1 == cp2); // true — code point equality

The practical implication for how to compare two characters in Java when those characters might be emoji or supplementary Unicode: do not assume one char equals one character. When working with arbitrary Unicode text, iterate by code point using String.codePoints() or codePointAt() rather than by char index.

// Code-point-safe iteration
String text = "Hello 😀 World";
text.codePoints().forEach(cp -> {
    System.out.printf("U+%04X (%s)%n", cp, new String(Character.toChars(cp)));
});
// Outputs each character's code point, including the emoji as one unit

This is one area where visual tooling pays dividends: when debugging Unicode issues, it is far easier to paste two text samples into a diff viewer and see exactly which code units differ than to parse hex output from printf.

Autoboxing & Caching: Hidden Comparison Pitfalls

JVM Autoboxing Cache: Character & Integer Character cache range (0 – 65535) Cached 0–127 Not cached (128–65535) == returns false for wrapper objects here 0 127 128 65535 Integer cache range (−128 – 2147483647) Cached -128–127 Not cached (128+) Integer i1=200; Integer i2=200; i1==i2 → false! Inside cache (safe with ==) Character c1 = 'A'; // 65 Character c2 = 'A'; // 65 c1 == c2 → true (cached) // same object from JVM cache Outside cache (== fails!) Character c3 = '€'; // 128 Character c4 = '€'; // 128 c3 == c4 → false (new obj) c3.equals(c4) → true ✓
The JVM caches Character objects for code points 0–127. Outside that range, == on wrapper objects silently returns false even for equal values.

Java's autoboxing converts a primitive char to a Character object automatically when the context requires it — for example, adding to a List<Character>. The JVM caches Character instances for code points 0 through 127 (the entire ASCII range). This means that Character.valueOf('A') called twice returns the same object, and == returns true.

Outside that range (code points 128–65535), Character.valueOf() allocates a new heap object each time, and == returns false even for equal values.

// Inside cache range (0-127): == appears to work
Character c1 = 'A'; // autoboxed via Character.valueOf('A'), cached
Character c2 = 'A'; // same cached instance
System.out.println(c1 == c2); // true — but only because of cache!

// Outside cache range (128+): == fails
Character c3 = '€'; // code point 128 — not cached
Character c4 = '€';
System.out.println(c3 == c4); // false — different heap objects
System.out.println(c3.equals(c4)); // true — correct!

// The Integer analog (same concept)
Integer i1 = 100; // cached (-128 to 127)
Integer i2 = 100;
System.out.println(i1 == i2); // true (cached)

Integer i3 = 200; // not cached
Integer i4 = 200;
System.out.println(i3 == i4); // false — classic Java gotcha

The safest rule: never use == to compare Character wrapper objects. Use equals() or unbox to primitive first. The autoboxing cache is a performance optimization, not a correctness guarantee — and code that relies on it will silently break when the character value falls outside the cached range.

This pitfall is especially subtle in unit tests that always test with ASCII characters. The tests pass, the cache makes == work for ASCII, and the bug only surfaces in production with extended Latin, Cyrillic, or CJK characters.

Common Comparison Bugs & Debugging Tips

Which Comparison Method? — Decision Flowchart Compare chars Primitive char or wrapper? primitive wrapper Need ordering? (sort / comparator) yes Character .compare() no a == b Need ordering? (sort / comparator) yes ch1.compareTo (ch2) no ch1.equals or Objects.equals() if null possible case-insensitive? Normalize first: Character.toLowerCase(c) supplementary Unicode? Use code points, not char! String.codePointAt(i) One emoji may be two char values (surrogate pair)
Follow this decision tree to pick the correct approach for any character comparison scenario in Java.

The following scenarios represent the most common java char comparison bugs encountered in real codebases, along with their fixes.

Bug 1: Using == on Character wrapper objects

// Broken — relies on cache; fails for code points above 127
public boolean isSeparator(Character ch) {
    return ch == Character.valueOf(','); // WRONG for values outside 0-127
}

// Fixed
public boolean isSeparator(Character ch) {
    return ch != null && ch.equals(','); // auto-unboxes ',' literal to Character
}
// Or unbox explicitly:
public boolean isSeparatorPrimitive(char ch) {
    return ch == ','; // primitive — == is correct
}

Bug 2: Comparing a char with a String

String input = getUserInput(); // returns "A" (a one-char string)
char expected = 'A';

// Broken — comparing types that can never be equal via ==
// This is a compile-time error in some contexts, runtime false in others
boolean match = input.equals(expected); // false — String vs char

// Fixed — extract the char first
boolean matchFixed = input.length() == 1 && input.charAt(0) == expected; // true

Bug 3: Off-by-one in case-insensitive search

// Broken — forgets that 'A' and 'a' have different code points
char search = 'a';
String text = "Apple Banana";
int count = 0;
for (char c : text.toCharArray()) {
    if (c == search) count++; // misses 'A' in "Apple" and 'a' in "Banana"
}
System.out.println(count); // 2 — only lowercase a's counted

// Fixed — normalize before comparing
for (char c : text.toCharArray()) {
    if (Character.toLowerCase(c) == Character.toLowerCase(search)) count++;
}
System.out.println(count); // 3 — A, a, a

Debugging Workflow with Diff Checker

When a character comparison bug is subtle — invisible whitespace, a Unicode lookalike, a zero-width joiner, or a surrogate pair rendered as a single glyph — visual tooling beats System.out.println. Paste the two code snippets or string values into Diff Checker and the tool highlights every differing character, including non-printable Unicode. This technique is equally useful for catching bugs in C++ string comparison code and other languages where invisible character differences cause silent failures.

For deeper static analysis of your Java comparison logic — such as catching misuse of == on objects at the linter level — see our guide on static code analysis tools for Java, which covers SpotBugs, PMD, and SonarQube rules that flag these exact patterns.

Summary: Which Method Should You Use?

Summary: Pick the Right Method at a Glance Type What you have Goal What you need Method Use this char primitive equality a == b numeric code point comparison char primitive ordering / sort Character.compare(a, b) or a < b / a > b Character wrapper equality ch1.equals(ch2) or Objects.equals() if null possible Character wrapper ordering / sort ch1.compareTo(ch2) implements Comparable<Character> char/Char either case-insensitive equality Character.toLowerCase(a) == Character.toLowerCase(b) NEVER use == on Character wrapper objects — use equals() or compare() instead
Quick reference: match your situation (type + goal) to the correct character comparison method in Java.

Here is a quick reference for every character compare Java scenario you will encounter:

Scenario Recommended Method Notes
Two primitive char values — equality a == b Safe; compares code points numerically
Two primitive char values — ordering a < b, a > b, or Character.compare(a, b) All equivalent for primitives
Character wrapper — equality ch1.equals(ch2) Never use == on wrappers
Character wrapper — ordering / sorting ch1.compareTo(ch2) or Character.compare(ch1, ch2) Both return negative/0/positive
Case-insensitive equality Character.toLowerCase(a) == Character.toLowerCase(b) Use String.equalsIgnoreCase() for locale-sensitive cases
Null-safe comparison of Character wrappers Objects.equals(ch1, ch2) Returns true if both null
Supplementary Unicode (emoji, historic scripts) Compare code points via String.codePointAt() Single char may be only half of a surrogate pair

Key Takeaways

  • Primitive char + == is safe, idiomatic, and efficient. It is the right default for how to compare two characters in Java when you control the types.
  • Character wrapper + == is wrong almost every time. It works by accident for cached ASCII values, but fails silently for code points above 127.
  • Character.compare() is the best choice when you need ordering (sorting, Comparator), because it communicates intent and avoids any arithmetic ambiguity.
  • Case-insensitive comparisons should normalize with Character.toLowerCase() first, or use String-level APIs for locale correctness.
  • Unicode beyond the BMP requires code-point iteration, not char indexing. Always test with supplementary characters if your app handles user-generated text.

Applying these rules consistently eliminates the majority of character compare Java bugs. When you are still unsure whether two character sequences match — or when the difference is invisible to the naked eye — a side-by-side visual diff is the fastest way to ground truth.

Frequently Asked Questions

What is the difference between == and equals() for chars in Java?

For two primitive char values, == performs a numeric code-point comparison and is the correct, idiomatic choice for how to compare char Java primitives. For Character wrapper objects, == compares object references rather than values, so it can return false for two Character objects that wrap the same letter. Use equals() (or Objects.equals() for null-safety) whenever you have Character wrappers — it always compares the wrapped char value.

How do I compare two characters case-insensitively in Java?

Normalize both characters to the same case before comparing: Character.toLowerCase(a) == Character.toLowerCase(b). Character.toUpperCase() works equivalently. These methods use Unicode default case mappings and are locale-independent. For locale-sensitive cases (such as Turkish dotted/dotless i), wrap the characters in single-character Strings and use String.equalsIgnoreCase() or pass an explicit Locale to toLowerCase().

What does Character.compare() return?

Character.compare(char x, char y) returns an int: 0 if x equals y, a negative value if x is less than y, and a positive value if x is greater than y. The exact non-zero result is the difference of their Unicode code points (x - y), but you should rely only on the sign, not the magnitude. It is the preferred way to implement Comparator lambdas or sort char arrays.

Can I use == on Character objects in Java?

Technically yes, but it is almost always a bug. The JVM caches Character instances for code points 0–127, so == may appear to work for ASCII letters because both references point to the same cached object. For code points 128 and above, Character.valueOf() allocates a new heap object every call, and == returns false even for equal values. Always use equals(), Objects.equals(), or unbox to primitive char first.

How do I compare chars with Unicode characters above U+FFFF?

A Java char is a 16-bit UTF-16 code unit and cannot hold a code point above U+FFFF on its own. Supplementary characters (most emoji, historic scripts, CJK extensions) are encoded as a surrogate pair of two char values. To compare them safely, work with code points using String.codePointAt(i), Character.toCodePoint(highSurrogate, lowSurrogate), or iterate via String.codePoints(). Comparing surrogate halves individually with == will give incorrect results for supplementary characters.