char в Java — это примитивный тип размером 16 бит без знака, который хранит одну кодовую единицу UTF-16, то есть число от 0 до 65535. Для латиницы и кириллицы одна такая единица совпадает с символом, но для эмодзи и части редких иероглифов символ занимает два char.
Содержание
Ниже разберем литералы, арифметику с char, типичные ошибки компиляции, суррогатные пары и методы класса Character. Все примеры прогнаны на OpenJDK 25.0.4 (Java 25 LTS) 23.09.2026.
Чтобы дальше не путаться, три термина:
| Термин | Что это | Пример для «😀» |
|---|---|---|
| Кодовая точка (code point) | номер символа в Unicode, до U+10FFFF | U+1F600 |
| Кодовая единица UTF-16 (code unit) | 16-битное число, ровно одно значение char | D83D и DE00 |
| Графема | то, что человек видит как один символ | один смайлик, но бывает из нескольких кодовых точек |
Место char среди примитивных типов
В Java восемь примитивных типов. char — единственный беззнаковый целочисленный среди них: у него нет отрицательных значений.
| Тип | Размер | Диапазон | Значение поля по умолчанию |
|---|---|---|---|
| byte | 8 бит | -128..127 | 0 |
| short | 16 бит | -32768..32767 | 0 |
| char | 16 бит | 0..65535 ('\u0000'..'\uffff') |
'\u0000' |
| int | 32 бита | около ±2,1 млрд | 0 |
| long | 64 бита | около ±9,2·10^18 | 0L |
| float | 32 бита | IEEE 754 | 0.0f |
| double | 64 бита | IEEE 754 | 0.0 |
| boolean | не задан спецификацией | true / false | false |
Размер boolean спецификация JVM не фиксирует: утверждение «boolean занимает 1 бит» неверно, на практике это обычно байт в массиве и больше в полях. Значения по умолчанию относятся к полям и элементам массивов, локальную переменную нужно инициализировать явно.
Минимальный пример: объявление и литералы
public class CharBasics {
public static void main(String[] args) {
char letter = 'A';
char fromCode = 1071; // десятичный код U+042F
char fromEscape = '\u042F'; // та же буква через Unicode-экранирование
char tab = '\t';
System.out.println(letter); // печатается как символ
System.out.println((int) letter); // код символа
System.out.println(fromCode + " " + fromEscape);
System.out.println(fromCode == fromEscape);
System.out.println("[" + tab + "]");
char next = letter;
next++; // ++ работает с char
System.out.println(next);
System.out.println((int) Character.MIN_VALUE + ".." + (int) Character.MAX_VALUE);
System.out.println(Character.SIZE + " bit, " + Character.BYTES + " bytes");
}
}
Вывод прогона:
A
65
Я Я
true
[ ]
B
0..65535
16 bit, 2 bytes
Что здесь важно:
- Символьный литерал пишется в одинарных кавычках:
'A'. Двойные кавычки даютString, а не char. - char можно задать числом (
1071), если это константа в диапазоне 0..65535. Отрицательное число или 65536 не скомпилируются. println(char)печатает символ, а(int) letter— его код. Хранится одно и то же число, различается только способ вывода.- Кириллица в консоли зависит от кодировки терминала: в Linux-контейнере вывелось «Я», в консоли Windows со старой кодовой страницей возможны вопросительные знаки.
Экранированные последовательности
| Запись | Символ | Код |
|---|---|---|
'\n' |
перевод строки | 10 |
'\t' |
табуляция | 9 |
'\'' |
одинарная кавычка | 39 |
'\\' |
обратный слеш | 92 |
'\u0041' |
любой символ по шестнадцатеричному коду | 65 (A) |
'\101' |
восьмеричная запись, от \0 до \377 |
65 (A) |
Арифметика с char: почему ‘a’ + ‘b’ = 195
char — числовой тип, поэтому его можно складывать и сравнивать. Но в арифметическом выражении char повышается до int, и результат — тоже int.
public class CharArithmetic {
public static void main(String[] args) {
char a = 'a';
char b = 'b';
System.out.println(a + b); // сложение кодов, тип int
System.out.println("" + a + b); // конкатенация строк
System.out.println(a + 1); // int
System.out.println((char) (a + 1)); // явное приведение обратно к char
char c = 'x';
c += 2; // составное присваивание приводит само
System.out.println(c);
char digit = '7';
System.out.println(digit - '0'); // 7
System.out.println(Character.getNumericValue(digit)); // 7
System.out.println((int) digit); // 55 - код, а не цифра
}
}
195
ab
98
b
z
7
7
55
'a' + 'b' дает 97 + 98 = 195, а не строку «ab». Чтобы склеить символы, выражение должно начинаться со строки. Приведение (int) '7' дает код 55, а не цифру 7: для цифры вычитают '0' или вызывают Character.getNumericValue.
Типичная ошибка: c = c + 1
Неверно:
public class CharError {
public static void main(String[] args) {
char c = 'a';
c = c + 1;
System.out.println(c);
}
}
Результат — javac отказывается компилировать:
CharError.java:4: error: incompatible types: possible lossy conversion from int to char
c = c + 1;
^
1 error
c + 1 имеет тип int, а неявно сузить int до char компилятор не разрешает. Исправление: c = (char) (c + 1); либо c += 1; или c++. Составное присваивание и инкремент делают приведение сами, поэтому в примере выше c += 2 сработал.
Ловушка: \u обрабатывается раньше компиляции
Последовательности \u javac заменяет на символы еще до разбора кода. Поэтому перевод строки через \u000a ломает литерал:
public class Escape {
public static void main(String[] args) {
char nl = '\u000a';
System.out.println((int) nl);
}
}
Escape.java:3: error: illegal line end in character literal
char nl = '\u000a';
^
1 error
Исправление: для перевода строки писать '\n', для кода 10 — (char) 10. То же касается \u000d и \u0027 (одинарная кавычка).
Суррогатные пары: когда один char — не символ
Когда в Java 1.0 появился char, Unicode умещался в 16 бит. Позже диапазон расширили до U+10FFFF, и символы за пределами U+FFFF (эмодзи, часть иероглифов, исторические письменности) стали кодироваться в UTF-16 двумя char: старшим и младшим суррогатом. Цепочка на одном примере: 😀 -> кодовая точка U+1F600 -> кодовые единицы D83D DE00.
public class Surrogates {
public static void main(String[] args) {
String s = "Hi \uD83D\uDE00"; // "Hi " + эмодзи U+1F600
System.out.println(s.length()); // кодовые единицы UTF-16
System.out.println(s.codePointCount(0, s.length())); // кодовые точки
char unit = s.charAt(3); // только половина эмодзи
System.out.printf("U+%04X high=%b%n", (int) unit, Character.isHighSurrogate(unit));
int cp = s.codePointAt(3); // целая кодовая точка
System.out.printf("U+%X, charCount=%d%n", cp, Character.charCount(cp));
// Неверно: разворот строки по char рвет суррогатную пару
StringBuilder naive = new StringBuilder();
for (int i = s.length() - 1; i >= 0; i--) naive.append(s.charAt(i));
naive.chars().forEach(u -> System.out.printf("%04X ", u));
System.out.println();
// Верно: StringBuilder.reverse() учитывает пары
new StringBuilder(s).reverse().codePoints()
.forEach(p -> System.out.printf("U+%04X ", p));
System.out.println();
System.out.println(new String(Character.toChars(0x1F600)).equals(s.substring(3)));
}
}
5
4
U+D83D high=true
U+1F600, charCount=2
DE00 D83D 0020 0069 0048
U+1F600 U+0020 U+0069 U+0048
true
Разбор:
length()вернул 5, хотя видимых символов четыре: смайлик занимает две кодовые единицы.charAt(3)вернул только старший суррогат D83D — это не символ, а половина пары.- Ручной разворот по char поставил DE00 перед D83D. Такая последовательность уже не валидный UTF-16, на экране вместо смайлика будут знаки замены.
- Для работы с полными символами используют
intи методы сcodePointв названии:codePointAt,codePointCount,codePoints(),Character.toChars.
Граница этой модели: и кодовая точка не всегда равна видимому символу. Флаги, эмодзи с оттенком кожи и буквы с комбинируемыми диакритиками состоят из нескольких кодовых точек. Например, флаг России — это 4 char, 2 кодовые точки и 1 графема. Для разбиения на графемы в JDK есть конструкция \X в регулярных выражениях (Pattern.compile("\\X") нашел в флаге ровно одно совпадение) и класс java.text.BreakIterator.
Класс Character: обертка и полезные методы
Character — класс-обертка над char. Он нужен там, где требуется объект: в коллекциях (List<Character>, Map<Character, Integer>), при сравнении с null. Переход между char и Character компилятор делает сам (автоупаковка), метод charValue() возвращает примитив явно.
public class CharMethods {
public static void main(String[] args) {
String input = "Otus 2026, Java!";
int letters = 0, digits = 0, spaces = 0, other = 0;
for (char ch : input.toCharArray()) {
if (Character.isLetter(ch)) letters++;
else if (Character.isDigit(ch)) digits++;
else if (Character.isWhitespace(ch)) spaces++;
else other++;
}
System.out.println(letters + " " + digits + " " + spaces + " " + other);
System.out.println(Character.toUpperCase('я'));
System.out.println(Character.isLetter('Я') + " " + Character.isDigit('٣'));
System.out.println(Character.getNumericValue('٣'));
System.out.println(Character.isLetter(0x1F600));
System.out.println(Character.getName('Я'));
}
}
8 4 2 2
Я
true true
3
false
CYRILLIC CAPITAL LETTER YA
Счетчик нашел 8 букв, 4 цифры, 2 пробела и 2 прочих знака (запятую и восклицательный). Литерал 'я' в исходнике требует, чтобы файл читался в UTF-8: это кодировка по умолчанию начиная с Java 18, на более старых JDK нужен флаг javac -encoding UTF-8.
| Метод | Что делает | На что обратить внимание |
|---|---|---|
isLetter, isDigit, isWhitespace |
проверяют категорию символа | работают по Unicode, не только ASCII |
toUpperCase, toLowerCase |
меняют регистр одного символа | для строк с учетом языка — String.toUpperCase(Locale) |
getNumericValue |
числовое значение цифры | для букв и римских цифр тоже возвращает числа |
isHighSurrogate, charCount |
работа с суррогатными парами | charCount = 2 для кодовых точек выше U+FFFF |
toChars, getName |
кодовая точка -> char[] и имя символа | принимают int, а не char |
Обратите внимание на isDigit('٣'): арабско-индийская цифра три считается цифрой. Если нужна проверка именно ASCII-цифр (номер телефона, код подтверждения), надежнее условие ch >= '0' && ch <= '9'.
Когда брать char, а когда String или int
| Задача | Что использовать | Почему |
|---|---|---|
| один служебный символ: разделитель, флаг, клавиша | char | компактно, сравнивается через == |
| текст от пользователя, имена, сообщения | String | может содержать эмодзи и суррогатные пары |
| перебор символов с учетом эмодзи | int и codePoints() |
char видит половинки пар |
| подсчет частоты символов в коллекции | Map<Character, Integer> или Map<Integer, Integer> по кодовым точкам |
коллекции хранят только объекты |
Выводы
- char — 16-битный беззнаковый тип (0..65535), хранит одну кодовую единицу UTF-16, а не обязательно целый символ.
- В арифметике char повышается до int:
'a' + 'b'= 195, аc = c + 1не компилируется без приведения;c++иc += 1работают. - Символы за пределами U+FFFF, включая эмодзи, занимают два char (суррогатную пару); для них нужны методы с
codePoint. \u-последовательности обрабатываются до компиляции, поэтому'\u000a'вызывает ошибку — используйте'\n'.- Класс
Characterдает проверки по Unicode; для строгой проверки ASCII-цифр нужен явный диапазон.
Где применяется / связь с практикой
Работа с char встречается в парсерах и валидаторах ввода, подсчете частот символов, обработке текста и в задачах на собеседованиях (палиндромы, анаграммы, разворот строки). Именно в таких задачах всплывают суррогатные пары и неявное повышение до int.
Освойте тему на практике
Если хотите системно разобрать типы данных, строки, коллекции и ООП на практике с проверкой кода преподавателем, посмотрите курс Java Developer. Basic. Попробовать формат бесплатно можно на открытых уроках Otus.
FAQ
Чем char в Java отличается от char в C?
В C char обычно 8 бит и может быть знаковым или беззнаковым в зависимости от компилятора, а в Java char всегда 16 бит без знака и хранит кодовую единицу UTF-16.
Можно ли сравнивать char через ==?
Да, для примитива char == сравнивает коды. Для объектов Character надежнее equals или распаковка в char, иначе сравниваются ссылки.
Можно ли использовать char в switch?
Да, char допустим в switch наравне с int и String, метки задаются символьными литералами вида case 'a' ->.



