String.charAt: примеры (JAVA)
String.charAt(int index): charОписание метода
Метод String.charAt в Java имеет сигнатуру
public char charAt(int index)и возвращает символ типа char из строки по указанной позиции. Индексация начинается с нуля. Возвращаемое значение представляет собой единицу UTF-16 (code unit), а не универсальный Unicode code point для символов за пределами базовой многобайтовой плоскости.
Аргументы и поведение:
- index: целое число (int). Допустимый диапазон: 0 ≤ index < length(), где length() - длина строки в кодовых единицах UTF-16.
Возвращаемое значение:
- char. Это UTF-16 code unit (16-битный символ). Для символов вне базовой многобайтовой плоскости (например, эмодзи) метод возвращает либо верхний (high) суррогат, либо нижний (low) суррогат в зависимости от индекса.
Исключения:
- Если index < 0 или index >= length(), бросается IndexOutOfBoundsException (в реализации строк обычно это подкласс StringIndexOutOfBoundsException) с соответствующим сообщением.
Особенности и замечания:
- Время выполнения - константное O(1), так как внутри строка хранится как массив кодовых единиц и доступ осуществляется по индексу.
- Строки в Java неизменяемы, поэтому доступ к символам безопасен для читателей из разных потоков без внешней синхронизации.
- Для работы с реальными Unicode code point рекомендуется использовать методы codePointAt, codePointCount и итерацию по code points, если требуется корректная обработка суррогатных пар.
Короткие примеры
Базовый пример извлечения символа по индексу.
public class Example1 {
public static void main(String[] args) {
String s = "Hello";
char c = s.charAt(1);
System.out.println(c);
}
}
e
Пример с выходом за пределы индекса и результатом исключения.
public class Example2 {
public static void main(String[] args) {
String s = "Hi";
System.out.println(s.charAt(5));
}
}
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 5
at java.base/java.lang.String.checkIndex(String.java:...)
at java.base/java.lang.String.charAt(String.java:...)
at Example2.main(Example2.java:...)
Поведение с суррогатной парой: символ вне BMP (например, эмодзи) занимает две кодовые единицы.
public class Example3 {
public static void main(String[] args) {
String s = "A????B"; // эмулирует символ за пределами BMP
System.out.println((int) s.charAt(1)); // кодовая единица (high или low суррогат)
System.out.println((int) s.charAt(2));
}
}
55357 56842
Альтернатива получения полного code point для позиции с учётом суррогатных пар.
public class Example4 {
public static void main(String[] args) {
String s = "A????B";
int cp = s.codePointAt(1);
System.out.println(Integer.toHexString(cp));
}
}
1f60a
Похожие методы в Java
Короткий список методов в Java, которые связаны с получением символов и их особенностями:
- codePointAt(int index): возвращает полный Unicode code point, корректно обрабатывает суррогатные пары. Предпочтительнее при работе с реальными Unicode символами.
- codePoints() (Stream<Integer>): поток кодовых точек для безопасной итерации по Unicode символам.
- toCharArray(): возвращает массив char для массовой работы с символами; полезно при необходимости модификации копии.
- substring(int, int): получение подстроки, в том числе длиной 1 для получения строки из одного char.
- StringBuilder.charAt(int) и StringBuffer.charAt(int): аналогичные методы для изменяемых буферов строк.
Аналоги в других языках
Краткие примечания и примеры по разным языкам с указанием отличий от Java.
PHP (байтовая индексация, для Unicode - mb_* функции):
$s = "Привет";
echo $s[2];
и (в зависимост от кодировки может быть байт, не символ)
// Для настоящих символов
echo mb_substr($s, 2, 1, 'UTF-8');
и
JavaScript (строки как последовательность UTF-16 code units; есть charAt и codePointAt):
let s = 'A????B';
console.log(s.charAt(1));
console.log(s.codePointAt(1).toString(16));
\uD83D 1f60a
Python (строки - последовательность Unicode code points):
s = 'A????B'
print(s[1])
print(ord(s[1]))
???? 128522
C# (поведение похоже на Java: индекс возвращает char - UTF-16 code unit):
string s = "A????B";
Console.WriteLine((int)s[1]);
55357
Go (строка - последовательность байтов; для rune/Unicode нужно преобразование):
s := "A????B"
fmt.Println(s[1]) // байт
r := []rune(s)
fmt.Printf("%U\n", r[1])
(значение байта) U+1F60A
Kotlin (аналогично Java, Char - UTF-16):
val s = "A????B"
println(s[1].code)
55357
Lua (интерфейс по умолчанию - байтовые подстроки; для Unicode - библиотека utf8):
s = "A????B"
print(string.sub(s, 2, 2)) -- индекс 1-based (байты)
print(utf8.codepoint(s, 2)) -- требует utf8
(часть байта) 128522
SQL (варианты зависят от СУБД):
SELECT SUBSTRING('Hello', 2, 1); -- позиция 1-based
e
Ключевые отличия: в Java и C# индекс возвращает UTF-16 единицу; в Python и некоторых языках индекс оперирует полноразмерными Unicode символами; в Go и Lua по умолчанию индекс работает с байтами, для Unicode требуется явное преобразование.
Типичные ошибки
Наиболее распространённые ошибки и примеры их проявления.
1. Выход за границы строки (IndexOutOfBounds).
public class Err1 {
public static void main(String[] args) {
String s = "X";
System.out.println(s.charAt(1));
}
}
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 1
at java.base/java.lang.String.checkIndex(String.java:...)
at java.base/java.lang.String.charAt(String.java:...)
at Err1.main(Err1.java:...)
2. Ошибочная обработка символов за пределами BMP - предположение, что char содержит полный Unicode символ.
String s = "\uD83D\uDE0A"; // эмоция как суррогатная пара
char c = s.charAt(0);
System.out.println(c);
System.out.println(Character.isHighSurrogate(c));
(нечитаемый символ) true
3. Приведение char к int без учета того, что это только кодовая единица UTF-16 и не совпадает с полным code point для суррогатных пар.
String s = "????";
System.out.println((int) s.charAt(0));
55357 // часть суррогатной пары, не 128522
4. Использование charAt при работе с байтовыми потоками или с разными кодировками без явного приведения кодировок.
Изменения и история
Метод charAt как таковой не претерпевал существенных изменений в современных версиях Java: сигнатура и поведение остаются прежними с ранних выпусков платформы. Развитие поддержки Unicode в API языка произошло через добавление методов для работы с кодовыми точками (codePointAt, codePoints и т. п.), которые появились для корректной обработки суррогатных пар и мультиязычных текстов. Депрекаций для charAt не было.
Расширенные и нестандартные примеры
1. Правильная итерация по Unicode code points с использованием offsetByCodePoints.
public class Adv1 {
public static void main(String[] args) {
String s = "A????Б";
for (int i = 0; i < s.length(); ) {
int cp = s.codePointAt(i);
System.out.printf("U+%X ", cp);
i += Character.charCount(cp);
}
}
}
U+41 U+1F60A U+41
2. Извлечение символа как строкового значения длиной 1 с учётом surrogate pair (без утраты информации при корректной позиции).
public class Adv2 {
public static void main(String[] args) {
String s = "A????B";
int idx = s.offsetByCodePoints(0, 1); // индекс первого code point после A
int cp = s.codePointAt(idx);
String ch = new String(Character.toChars(cp));
System.out.println(ch);
}
}
????
3. Высокопроизводительная обработка символов в цикле при знании, что вход ASCII-only.
public class Adv3 {
public static void main(String[] args) {
String s = "Ascii text repeated...";
int sum = 0;
for (int i = 0, n = s.length(); i < n; i++) {
sum += s.charAt(i); // быстрый доступ к UTF-16 единице
}
System.out.println(sum);
}
}
(целочисленная сумма кодов символов)
4. Комбинация с потоками: преобразование строки в поток кодовых точек для функциональной обработки.
public class Adv4 {
public static void main(String[] args) {
String s = "A????B";
s.codePoints().forEach(cp -> System.out.printf("U+%X ", cp));
}
}
U+41 U+1F60A U+42
5. Реконструкция строки из массива char, когда требуется модификация отдельных кодовых единиц.
public class Adv5 {
public static void main(String[] args) {
String s = "hello";
char[] a = s.toCharArray();
a[0] = 'H';
String s2 = new String(a);
System.out.println(s2);
}
}
Hello
6. Пример поиска символов по условию с использованием charAt и индексной арифметики.
public class Adv6 {
public static void main(String[] args) {
String s = "a1b2c3";
StringBuilder digits = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (Character.isDigit(ch)) digits.append(ch);
}
System.out.println(digits.toString());
}
}
123