문자열 순회

드코미·2026년 6월 20일
post-thumbnail

Java에서 문자열을 한 글자씩 순회하는 방법

"왜 String은 for-each가 안 될까?"

문자열을 다루다 보면 자연스럽게 이런 코드를 시도하게 됩니다.

String s = "hello";

for (char c : s) {  // 어쩐지 될 것 같은데...
    System.out.println(c);
}

하지만 이 코드는 컴파일조차 되지 않습니다.

error: incompatible types: String cannot be converted to Iterable<...>

배열이나 List는 for-each(for (타입 변수 : 컬렉션))로 순회가 되는데, 왜 String은 안 되는 걸까요?

Iterable이 핵심이다

자바의 for-each문은 사실 마법이 아닙니다. Iterable 인터페이스를 구현한 객체에 대해서만 동작하는 문법입니다.

List<Integer> list = List.of(1, 2, 3);
for (int x : list) { ... }  // List는 Iterable을 구현하고 있어서 가능

int[], char[] 같은 배열도 (정확히는 Iterable을 구현한 게 아니라 언어 차원에서 별도로 지원하지만) for-each가 가능합니다. 그런데 String 클래스는 Iterable을 구현하고 있지 않습니다. 그래서 String 자체를 for-each 대상으로 넣을 수가 없는 거예요.

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    // Iterable이 안 보임
}

실제 String 클래스 선언을 봐도 Iterable은 구현 목록에 없습니다. 그래서 우리는 String을 "순회 가능한 형태"로 먼저 바꿔줘야 합니다.

방법 1: toCharArray() — 가장 많이 쓰는 방식

toCharArray()는 문자열을 char[] 배열로 변환해주는 메서드입니다.

String s = "hello";
char[] arr = s.toCharArray();  // ['h', 'e', 'l', 'l', 'o']

for (char c : arr) {
    System.out.println(c);
}

배열은 for-each가 가능하니, 이렇게 변환만 해주면 깔끔하게 순회할 수 있습니다. 실무에서는 변수에 따로 담지 않고 한 줄로 줄여서 많이 씁니다.

for (char c : s.toCharArray()) {
    System.out.println(c);
}

장점: 코드가 간결하고 직관적입니다. "이 문자열의 글자 하나하나에 대해 뭔가 처리하고 싶다"는 의도가 바로 드러납니다.

단점: 매번 호출할 때마다 새로운 배열을 만드는 비용이 발생합니다. 문자열이 아주 길고 반복 호출이 많은 상황이라면 약간의 성능 차이가 생길 수 있습니다. (코딩테스트 수준에서는 거의 신경 쓸 필요 없습니다.)

방법 2: charAt(i) + 인덱스 기반 for문

인덱스(위치 정보)가 같이 필요한 경우에는 일반적인 for문과 charAt()을 조합합니다.

String s = "hello";

for (int i = 0; i < s.length(); i++) {
    char c = s.charAt(i);
    System.out.println(i + "번째 글자: " + c);
}

charAt(i)는 i번째 위치의 문자 하나를 꺼내오는 메서드입니다. for-each와 달리 인덱스를 직접 다루기 때문에, "앞뒤 글자와 비교해야 한다", "특정 위치부터 거꾸로 봐야 한다" 같은 요구사항에 유리합니다.

// 예: 바로 다음 글자와 비교해야 하는 경우
for (int i = 0; i < s.length() - 1; i++) {
    if (s.charAt(i) == s.charAt(i + 1)) {
        System.out.println("연속된 같은 글자 발견: " + s.charAt(i));
    }
}

이런 코드는 toCharArray() + for-each로는 쓰기 까다롭습니다. 인덱스가 없으니까요.

방법 3: chars() (스트림 방식)

자바 8 이후로는 스트림을 활용한 방식도 있습니다.

String s = "hello";

s.chars().forEach(c -> System.out.println((char) c));

chars()IntStream을 반환하기 때문에, 각 요소가 char가 아니라 int(유니코드 코드값)로 다뤄집니다. 그래서 출력할 때 (char)로 캐스팅이 필요합니다. 스트림 문법에 익숙하다면 필터링이나 변환 작업을 체이닝하기에 편리하지만, 코딩테스트에서는 자주 쓰이는 방식은 아닙니다.

세 가지 방식 비교

방식인덱스 필요?코드 스타일추천 상황
toCharArray() + for-each❌ 불필요가장 간결그냥 글자 하나씩 순서대로 처리할 때
charAt(i) + for문✅ 필요약간 더 길어짐인덱스, 이전/다음 글자 비교가 필요할 때
chars() 스트림❌ 불필요함수형 스타일스트림 체이닝이 필요한 경우

실전 예시: 괄호 짝 검사

스택을 활용한 괄호 검사 문제로 toCharArray() 방식을 적용해보면 다음과 같습니다.

boolean isValid(String s) {
    Deque<Character> stack = new ArrayDeque<>();
    
    for (char c : s.toCharArray()) {
        if (c == '(') {
            stack.push(c);
        } else if (c == ')') {
            if (stack.isEmpty()) {
                return false;  // 짝이 안 맞음
            }
            stack.pop();
        }
    }
    
    return stack.isEmpty();  // 다 짝지어졌으면 true
}

이 문제는 인덱스가 필요 없고 "글자 하나하나를 순서대로 보면서 여는 괄호면 쌓고, 닫는 괄호면 빼는" 단순한 흐름이기 때문에 toCharArray() + for-each가 가장 깔끔합니다.

반면 "i번째 글자와 i+2번째 글자를 비교해야 한다" 같은 요구사항이 생기면 인덱스 기반 for문으로 바꿔야 합니다.

정리

  • StringIterable을 구현하지 않아서 for (char c : s)처럼 직접 순회할 수 없습니다.
  • 가장 많이 쓰는 해결책은 s.toCharArray()로 배열로 바꾼 뒤 for-each를 쓰는 것입니다.
  • 인덱스 정보가 필요하면 charAt(i)와 일반 for문을 씁니다.
  • 어떤 방식을 쓰든 핵심은 같습니다: 문자열을 글자 단위로 쪼개서, 한 글자씩 어떤 처리를 반복하는 것.

문자열 순회는 코딩테스트에서 정말 자주 등장하는 패턴이라, toCharArray()charAt() 두 가지만 손에 익혀두면 대부분의 문제에서 막힘없이 풀어나갈 수 있습니다.

profile
할 수 있다!!!

0개의 댓글