Для хранения строк в Java в общем случае используется кодировка UTF-16. Для полного понимания вопроса необходимо понимать ее особенности: что такое суррогатные пары и плоскости юникода. Конкретнее, что один символ в обычном понимании слова называется code point, и представляется одним или двумя значениями типа char. Значит, корректный ответ нужно начать с уточнения: подразумевается ли обход значений char или code point.

Из этого следует, что и длина строки, верхняя граница итерации – тоже понятие неоднозначное. Метод length() на самом деле возвращает не количество Unicode-символов, а количество значений char. Реальную логическую длину (количество кодовых точек) покажет метод codePointCount(). Нужно помнить, что чтобы обнаружить суррогатные пары, внутри ему придется проитерировать по символам.

Итерироваться по char можно либо получая по одному символу с помощью метода charAt(), либо со всем массивом сразу, методом toCharArray(). В случае с массивом создается копия внутреннего значения – это естественно медленнее, зато этот массив-копию можно мутировать. Работать с суррогатными парами придется вручную, для этого в классе Character есть специальные методы.

Другой вариант – сразу оперировать методами codePointAt() и codePointBefore(). Параметром указываются индексы массива char, но результатом будут кодовые точки типа int.

В Java 8 появился новый удобный способ обхода – методы chars() и codePoints(). Они возвращают IntStream из символов и кодовых точек соответственно. Носителем результирующих стримов выступает оригинальное внутреннее значение строки, копирование массива не требуется.