본문 바로가기

문자열 변환과 StringBuilder

문자열 반환값 누락을 고치고 값 변환·서식화·반복 결합에서 String과 StringBuilder를 선택하는 기준을 실행으로 확인합니다.

String은 불변이므로 결합과 변환마다 결과 값을 반환합니다.

짧은 표현식은 컴파일러가 효율적으로 최적화하지만, 반복문에서 누적 결합하면 중간 문자열과 복사가 많이 생길 수 있습니다.

StringBuilder는 한 작업 범위 안에서 가변 버퍼를 사용한 뒤 마지막에 String으로 확정합니다.


반환값 누락과 문자열 불변성

불변 메서드를 가변 setter처럼 호출하면 변화가 없습니다.

다음 프로그램은 접미어를 붙였다고 생각하지만 반환된 새 문자열을 저장하지 않았습니다.

lab/IgnoredStringResultBug.java
public final class IgnoredStringResultBug {
    public static void main(String[] args) {
        String title = "Object";

        title.concat(" / String");
        title.toUpperCase();

        System.out.println("title=" + title);
    }
}
잘못된 결과
title=Object

title = title.concat(...)처럼 결과를 다시 대입하거나 새 변수에 받습니다.

메서드 체인으로 여러 변환을 연결할 때도 마지막 결과를 저장해야 합니다.

원본 보존이 목적이면 expanded 같은 별도 이름이 의도를 드러냅니다.


문자열 변환과 서식

String.valueOf는 기본형과 객체를 문자열로 바꿉니다.

객체는 문자 표현 계약을 사용하고 null은 문자열 "null"이 됩니다.

이 설명은 String.valueOf(Object)에 해당합니다. 예를 들어 Object value = null을 넘기면 "null"을 얻습니다. 인수에 타입 없이 null만 쓰는 String.valueOf(null)은 char[] 오버로드가 선택되어 NullPointerException이 납니다. null을 허용하는 객체 변환을 의도했다면 Object 변수나 (Object) null로 그 타입을 드러냅니다.

formatted나 String.format은 자리표시자에 맞춰 숫자 정밀도와 배치를 표현합니다.

src/BoardSummaryFormatting.java
public final class BoardSummaryFormatting {
    public static void main(String[] args) {
        int viewCount = 95;
        int likeCount = 19;
        boolean published = true;
        double likeRate = likeCount * 100.0 / viewCount;

        String summary = "title=%s, views=%d, likes=%d, likeRate=%.1f%%, published=%b"
                .formatted("문자열 질문", viewCount, likeCount, likeRate, published);

        System.out.println(summary);
    }
}
title=문자열 질문, views=95, likes=19, likeRate=20.0%, published=true

서식 문자열과 인수 타입이 맞지 않으면 실행 중 예외가 발생할 수 있습니다.

고정된 내부 로그에는 편리하지만 사용자 번역 문구와 지역별 숫자 표기에는 Locale을 함께 고려합니다.

구조화 로그나 데이터 교환은 문자열 하나보다 전용 필드나 직렬화 도구를 사용합니다.


문자열 결합의 처리 과정

상수 리터럴끼리의 결합은 컴파일 시 하나로 합쳐질 수 있습니다.

변수 몇 개를 잇는 짧은 표현식도 컴파일러가 내부 결합 코드를 효율적으로 만들 수 있으므로 무조건 StringBuilder로 바꿀 필요는 없습니다.

src/ReadableStringComposition.java
public final class ReadableStringComposition {
    public static void main(String[] args) {
        String title = "string";
        int viewCount = 45;

        String constant = "Board" + " Log";
        String line = constant + ": " + title + "=" + viewCount;

        System.out.println(constant);
        System.out.println(line);
    }
}
Board Log
Board Log: string=45

한 줄짜리 메시지는 +가 가장 읽기 쉽습니다.

성능을 추측해 수동 builder 코드를 늘리기보다 반복 횟수, 데이터 크기, 호출 빈도를 측정합니다.

문자열 템플릿 생성이 병목으로 확인되면 그 좁은 범위만 개선합니다.


StringBuilder 가변 버퍼

루프에서 result = result + item을 반복하면 매번 지금까지의 내용을 복사한 새 String이 필요합니다.

StringBuilder는 내부 버퍼에 append하고 한 번만 최종 문자열을 만듭니다.

src/BoardStringBuilder.java
public final class BoardStringBuilder {
    public static void main(String[] args) {
        Post[] entries = {
                new Post("object", 40),
                new Post("immutable", 50),
                new Post("string", 45)
        };

        StringBuilder builder = new StringBuilder("게시판");
        for (Post entry : entries) {
            builder.append('\n')
                    .append("- ")
                    .append(entry.title())
                    .append(": ")
                    .append(entry.viewCount())
                    .append('m');
        }

        String report = builder.toString();
        System.out.println(report);
    }

    private record Post(String title, int viewCount) { }
}
게시판
- object: 40m
- immutable: 50m
- string: 45m

StringBuilder는 가변이므로 여러 스레드나 장기 필드에서 공유하지 않고 한 메서드 안의 조립 도구로 사용하는 것이 안전합니다.

toString()으로 결과를 확정한 뒤 외부에는 불변 String만 반환합니다.

원 출력의 40m·50m·45m에서 m은 append('m')로 넣은 문자입니다. 조회 수가 시간 단위로 바뀐 것이 아닙니다. 실제 조회 수 표기 요구가 "40회"라면 해당 접미 문자열을 "회"로 바꾸는 별도 변형을 만들고 세 줄의 순서와 값이 유지되는지 확인합니다. 숫자와 접미 문자는 builder에 각각 이어 붙인 조각입니다.

반환값을 버리는 String 호출과 지역 builder의 조립을 구분한다

String 반환값을 무시해 title을 유지하는 실행과 별도 builder에서 append 후 toString을 반환하는 실행을 비교합니다. 원문 m 문자와 builder가 동결되지 않는 조건을 유지한 값/버퍼 변화입니다.

반환값을 버리는 String 호출과 지역 builder의 조립을 구분한다

IgnoredStringResultBug는 String 반환값을 버리고 BoardStringBuilder는 지역 버퍼를 변경한 뒤 결과 String을 report에 받는다.

String은 결과를 반환하고 builder는 내부 버퍼를 바꿉니다

서로 독립된 두 프로그램을 비교합니다. IgnoredStringResultBug의 두 호출은 연속 체인이 아니라 같은 title을 대상으로 한 별도 문장입니다.

String은 결과를 반환하고 builder는 내부 버퍼를 바꿉니다 독립된실패String프로그램에서concat과대문자변환은둘다원Object문자값에서각각호출하고반환을버려최종titleObject이다. 별도builder프로그램은세입력을append로누적하고toString결과를report에저장해main이출력한다. m은접미문자이며toString이builder를동결하지않는다. String 실패 예제 · 독립 실행 title의 원본 Object concat 반환 Object / String / 저장 안 함 toUpperCase 반환 OBJECT / 저장 안 함 builder 예제 · 독립 실행 입력 배열 object40 / immutable50 / string45 지역 builder append로 버퍼 변경 report의 String toString으로 값 획득 main 출력 3줄의 값과 순서 유지 최종 title=Object m은 append한 문자 · toString은 builder를 동결하지 않음
String은 결과를 반환하고 builder는 내부 버퍼를 바꿉니다 독립된실패String프로그램에서concat과대문자변환은둘다원Object문자값에서각각호출하고반환을버려최종titleObject이다. 별도builder프로그램은세입력을append로누적하고toString결과를report에저장해main이출력한다. m은접미문자이며toString이builder를동결하지않는다. String 실패 예제 · 독립 실행 title의 원본 Object concat 반환 Object / String 저장 안 함 toUpperCase 반환 OBJECT 저장 안 함 둘 다 같은 원본에서 호출 최종 title=Object builder 예제 · 독립 실행 입력 배열 object 40 / immutable50 string 45 지역 builder append로 버퍼 변경 report의 String toString으로 값 획득 main 출력 3줄의 값과 순서 유지 m은 append한 문자 toString은 builder 동결 아님
무시된 반환값과 실제로 보관한 결과
프로그램·호출변경 대상사용한 결과
반환 누락

concat

title의 문자열은 불변. 변수에 재대입하지 않음반환값을 버림. 다음 문장도 "Object"에서 시작
반환 누락

toUpperCase

원본 title을 바꾸지 않음반환값을 버림. 최종 출력은 title=Object
지역 조립

append

BoardStringBuilder의 지역 builder 내용을 반복해서 변경입력 배열의 순서대로 세 게시글을 누적
문자열 결과

toString

완성한 내용을 String으로 얻음. builder를 동결하는 호출은 아님report에 받고 println(report)으로 출력
반환 누락 · concat
변경 대상: title의 문자열은 불변. 변수에 재대입하지 않음
사용한 결과: 반환값을 버림. 다음 문장도 "Object"에서 시작
반환 누락 · toUpperCase
변경 대상: 원본 title을 바꾸지 않음
사용한 결과: 반환값을 버림. 최종 출력은 title=Object
지역 조립 · append
변경 대상: BoardStringBuilder의 지역 builder 내용을 반복해서 변경
사용한 결과: 입력 배열의 순서대로 세 게시글을 누적
문자열 결과 · toString
변경 대상: 완성한 내용을 String으로 얻음. builder를 동결하는 호출은 아님
사용한 결과: report에 받고 println(report)으로 출력

BoardStringBuilder의 실제 출력은 다음과 같습니다. 이 main은 출력할 뿐, String을 호출자에게 반환하는 별도 서비스 메서드는 없습니다.

게시판
- object: 40m
- immutable: 50m
- string: 45m

짧은 결합을 모두 builder로 바꾸거나 성능 우열을 단정하지 않습니다. 내부 버퍼의 용량·복사 횟수와 실행 시간은 이 출력만으로 측정되지 않습니다.

이 예의 조건

  • IgnoredStringResultBug와 BoardStringBuilder는 독립 프로그램입니다. 입력 준비→각 변경 호출→반환·출력의 공통 관찰 단계마다 비교합니다.
  • builder의 m은 append한 접미 문자이며 시간 단위가 아닙니다. toString이 builder를 동결하지 않습니다.
관계도의 보충 설명
원 도표의 전체 관계·라벨·조건
구분내용
2독립된 실패 String 프로그램에서 concat과 대문자 변환은 둘 다 원 Object 문자 값에서 각각 호출하고 반환값을 버려 최종 title은 Object이다. 별도 builder 프로그램은 세 입력을 append로 누적하고 toString 결과를 report에 저장해 main이 출력한다. m은 접미 문자이며 toString이 builder를 동결하지 않는다.
8String
15object40
16immutable50
17string45
원 도표의 설명과 주석
구분내용
설명독립된 실패 String 프로그램에서 concat과 대문자 변환은 둘 다 원 Object 문자 값에서 각각 호출하고 반환값을 버려 최종 title은 Object이다. 별도 builder 프로그램은 세 입력을 append로 누적하고 toString 결과를 report에 저장해 main이 출력한다. m은 접미 문자이며 toString이 builder를 동결하지 않는다.
전체 읽기 정보반환값을 버리는 String 호출과 지역 builder의 조립을 구분한다 반환값을 버리는 String 호출과 지역 builder의 조립을 구분한다 IgnoredStringResultBug는 String 반환값을 버리고 BoardStringBuilder는 지역 버퍼를 변경한 뒤 결과 String을 report에 받는다. String은 결과를 반환하고 builder는 내부 버퍼를 바꿉니다 서로 독립된 두 프로그램을 비교합니다. IgnoredStringResultBug 의 두 호출은 연속 체인이 아니라 같은 title 을 대상으로 한 별도 문장입니다. 무시된 반환값과 실제로 보관한 결과 프로그램·호출 변경 대상 사용한 결과 반환 누락 concat title 의 문자열은 불변. 변수에 재대입하지 않음 반환값을 버림. 다음 문장도 "Object" 에서 시작 반환 누락 toUpperCase 원본 title 을 바꾸지 않음 반환값을 버림. 최종 출력은 title=Object 지역 조립 append BoardStringBuilder 의 지역 builder 내용을 반복해서 변경 입력 배열의 순서대로 세 게시글을 누적 문자열 결과 toString 완성한 내용을 String으로 얻음. builder를 동결하는 호출은 아님 report 에 받고 println(report) 으로 출력 반환 누락 · concat 변경 대상: title 의 문자열은 불변. 변수에 재대입하지 않음 사용한 결과: 반환값을 버림. 다음 문장도 "Object" 에서 시작 반환 누락 · toUpperCase 변경 대상: 원본 title 을 바꾸지 않음 사용한 결과: 반환값을 버림. 최종 출력은 title=Object 지역 조립 · append 변경 대상: BoardStringBuilder 의 지역 builder 내용을 반복해서 변경 사용한 결과: 입력 배열의 순서대로 세 게시글을 누적 문자열 결과 · toString 변경 대상: 완성한 내용을 String으로 얻음. builder를 동결하는 호출은 아님 사용한 결과: report 에 받고 println(report) 으로 출력 BoardStringBuilder 의 실제 출력은 다음과 같습니다. 이 main은 출력할 뿐, String을 호출자에게 반환하는 별도 서비스 메서드는 없습니다. 게시판 - object: 40m - immutable: 50m - string: 45m 짧은 결합을 모두 builder로 바꾸거나 성능 우열을 단정하지 않습니다. 내부 버퍼의 용량·복사 횟수와 실행 시간은 이 출력만으로 측정되지 않습니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
각 프로그램의 입력 준비

String 사례는 title Object, builder 사례는 세 게시글과 게시판 초기 버퍼를 준비합니다.

  • String 입력: title="Object"
  • builder 입력: object 40, immutable 50, string 45
  • builder 초기 버퍼: 게시판
  • 두 프로그램의 상태와 반환값 소유를 서로 독립으로 고정합니다.
각 문자열 호출과 버퍼 변경

String 반환값은 버리고 builder는 각 게시글의 줄을 자신의 버퍼에 append합니다.

  • concat 반환: Object / String, title에 저장하지 않음
  • 대문자 반환: OBJECT, title에 저장하지 않음
  • builder append: 각 줄의 제목·숫자·m을 순서대로 누적
  • String 호출은 원 title을 바꾸지 않지만 append는 지역 builder 버퍼를 바꿉니다.
각 반환과 main 출력

String main은 원 title을 출력하고 builder main은 toString 결과를 report에 받아 출력합니다.

  • String 출력: title=Object
  • builder 반환: report는 게시판 헤더와 object 40m·immutable 50m·string 45m의 세 줄
  • builder 출력: main이 report를 출력, 입력 순서 유지
  • builder 상태: toString 이후에도 가변, 자동 동결 아님
  • 버린 반환값과 채택한 반환값의 관찰 차이를 보여 주며 두 main 사이에 실행 연결은 없습니다.

반환값을 버리는 String 호출과 지역 builder의 조립을 구분한다

String 반환값을 무시해 title을 유지하는 실행과 별도 builder에서 append 후 toString을 반환하는 실행을 비교합니다. 원문 m 문자와 builder가 동결되지 않는 조건을 유지한 값/버퍼 변화입니다.

반환값을 버리는 String 호출과 지역 builder의 조립을 구분한다

IgnoredStringResultBug는 String 반환값을 버리고 BoardStringBuilder는 지역 버퍼를 변경한 뒤 결과 String을 report에 받는다.

String은 결과를 반환하고 builder는 내부 버퍼를 바꿉니다

서로 독립된 두 프로그램을 비교합니다. IgnoredStringResultBug의 두 호출은 연속 체인이 아니라 같은 title을 대상으로 한 별도 문장입니다.

String은 결과를 반환하고 builder는 내부 버퍼를 바꿉니다 독립된실패String프로그램에서concat과대문자변환은둘다원Object문자값에서각각호출하고반환을버려최종titleObject이다. 별도builder프로그램은세입력을append로누적하고toString결과를report에저장해main이출력한다. m은접미문자이며toString이builder를동결하지않는다. String 실패 예제 · 독립 실행 title의 원본 Object concat 반환 Object / String / 저장 안 함 toUpperCase 반환 OBJECT / 저장 안 함 builder 예제 · 독립 실행 입력 배열 object40 / immutable50 / string45 지역 builder append로 버퍼 변경 report의 String toString으로 값 획득 main 출력 3줄의 값과 순서 유지 최종 title=Object m은 append한 문자 · toString은 builder를 동결하지 않음
String은 결과를 반환하고 builder는 내부 버퍼를 바꿉니다 독립된실패String프로그램에서concat과대문자변환은둘다원Object문자값에서각각호출하고반환을버려최종titleObject이다. 별도builder프로그램은세입력을append로누적하고toString결과를report에저장해main이출력한다. m은접미문자이며toString이builder를동결하지않는다. String 실패 예제 · 독립 실행 title의 원본 Object concat 반환 Object / String 저장 안 함 toUpperCase 반환 OBJECT 저장 안 함 둘 다 같은 원본에서 호출 최종 title=Object builder 예제 · 독립 실행 입력 배열 object 40 / immutable50 string 45 지역 builder append로 버퍼 변경 report의 String toString으로 값 획득 main 출력 3줄의 값과 순서 유지 m은 append한 문자 toString은 builder 동결 아님
무시된 반환값과 실제로 보관한 결과
프로그램·호출변경 대상사용한 결과
반환 누락

concat

title의 문자열은 불변. 변수에 재대입하지 않음반환값을 버림. 다음 문장도 "Object"에서 시작
반환 누락

toUpperCase

원본 title을 바꾸지 않음반환값을 버림. 최종 출력은 title=Object
지역 조립

append

BoardStringBuilder의 지역 builder 내용을 반복해서 변경입력 배열의 순서대로 세 게시글을 누적
문자열 결과

toString

완성한 내용을 String으로 얻음. builder를 동결하는 호출은 아님report에 받고 println(report)으로 출력
반환 누락 · concat
변경 대상: title의 문자열은 불변. 변수에 재대입하지 않음
사용한 결과: 반환값을 버림. 다음 문장도 "Object"에서 시작
반환 누락 · toUpperCase
변경 대상: 원본 title을 바꾸지 않음
사용한 결과: 반환값을 버림. 최종 출력은 title=Object
지역 조립 · append
변경 대상: BoardStringBuilder의 지역 builder 내용을 반복해서 변경
사용한 결과: 입력 배열의 순서대로 세 게시글을 누적
문자열 결과 · toString
변경 대상: 완성한 내용을 String으로 얻음. builder를 동결하는 호출은 아님
사용한 결과: report에 받고 println(report)으로 출력

BoardStringBuilder의 실제 출력은 다음과 같습니다. 이 main은 출력할 뿐, String을 호출자에게 반환하는 별도 서비스 메서드는 없습니다.

게시판
- object: 40m
- immutable: 50m
- string: 45m

짧은 결합을 모두 builder로 바꾸거나 성능 우열을 단정하지 않습니다. 내부 버퍼의 용량·복사 횟수와 실행 시간은 이 출력만으로 측정되지 않습니다.

이 예의 조건

  • IgnoredStringResultBug와 BoardStringBuilder는 독립 프로그램입니다. 입력 준비→각 변경 호출→반환·출력의 공통 관찰 단계마다 비교합니다.
  • builder의 m은 append한 접미 문자이며 시간 단위가 아닙니다. toString이 builder를 동결하지 않습니다.

관계도의 보충 설명

원 도표의 전체 관계·라벨·조건
구분내용
2독립된 실패 String 프로그램에서 concat과 대문자 변환은 둘 다 원 Object 문자 값에서 각각 호출하고 반환값을 버려 최종 title은 Object이다. 별도 builder 프로그램은 세 입력을 append로 누적하고 toString 결과를 report에 저장해 main이 출력한다. m은 접미 문자이며 toString이 builder를 동결하지 않는다.
8String
15object40
16immutable50
17string45
원 도표의 설명과 주석
구분내용
설명독립된 실패 String 프로그램에서 concat과 대문자 변환은 둘 다 원 Object 문자 값에서 각각 호출하고 반환값을 버려 최종 title은 Object이다. 별도 builder 프로그램은 세 입력을 append로 누적하고 toString 결과를 report에 저장해 main이 출력한다. m은 접미 문자이며 toString이 builder를 동결하지 않는다.
전체 읽기 정보반환값을 버리는 String 호출과 지역 builder의 조립을 구분한다 반환값을 버리는 String 호출과 지역 builder의 조립을 구분한다 IgnoredStringResultBug는 String 반환값을 버리고 BoardStringBuilder는 지역 버퍼를 변경한 뒤 결과 String을 report에 받는다. String은 결과를 반환하고 builder는 내부 버퍼를 바꿉니다 서로 독립된 두 프로그램을 비교합니다. IgnoredStringResultBug 의 두 호출은 연속 체인이 아니라 같은 title 을 대상으로 한 별도 문장입니다. 무시된 반환값과 실제로 보관한 결과 프로그램·호출 변경 대상 사용한 결과 반환 누락 concat title 의 문자열은 불변. 변수에 재대입하지 않음 반환값을 버림. 다음 문장도 "Object" 에서 시작 반환 누락 toUpperCase 원본 title 을 바꾸지 않음 반환값을 버림. 최종 출력은 title=Object 지역 조립 append BoardStringBuilder 의 지역 builder 내용을 반복해서 변경 입력 배열의 순서대로 세 게시글을 누적 문자열 결과 toString 완성한 내용을 String으로 얻음. builder를 동결하는 호출은 아님 report 에 받고 println(report) 으로 출력 반환 누락 · concat 변경 대상: title 의 문자열은 불변. 변수에 재대입하지 않음 사용한 결과: 반환값을 버림. 다음 문장도 "Object" 에서 시작 반환 누락 · toUpperCase 변경 대상: 원본 title 을 바꾸지 않음 사용한 결과: 반환값을 버림. 최종 출력은 title=Object 지역 조립 · append 변경 대상: BoardStringBuilder 의 지역 builder 내용을 반복해서 변경 사용한 결과: 입력 배열의 순서대로 세 게시글을 누적 문자열 결과 · toString 변경 대상: 완성한 내용을 String으로 얻음. builder를 동결하는 호출은 아님 사용한 결과: report 에 받고 println(report) 으로 출력 BoardStringBuilder 의 실제 출력은 다음과 같습니다. 이 main은 출력할 뿐, String을 호출자에게 반환하는 별도 서비스 메서드는 없습니다. 게시판 - object: 40m - immutable: 50m - string: 45m 짧은 결합을 모두 builder로 바꾸거나 성능 우열을 단정하지 않습니다. 내부 버퍼의 용량·복사 횟수와 실행 시간은 이 출력만으로 측정되지 않습니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
각 프로그램의 입력 준비

String 사례는 title Object, builder 사례는 세 게시글과 게시판 초기 버퍼를 준비합니다.

  • String 입력: title="Object"
  • builder 입력: object 40, immutable 50, string 45
  • builder 초기 버퍼: 게시판
  • 두 프로그램의 상태와 반환값 소유를 서로 독립으로 고정합니다.
각 문자열 호출과 버퍼 변경

String 반환값은 버리고 builder는 각 게시글의 줄을 자신의 버퍼에 append합니다.

  • concat 반환: Object / String, title에 저장하지 않음
  • 대문자 반환: OBJECT, title에 저장하지 않음
  • builder append: 각 줄의 제목·숫자·m을 순서대로 누적
  • String 호출은 원 title을 바꾸지 않지만 append는 지역 builder 버퍼를 바꿉니다.
각 반환과 main 출력

String main은 원 title을 출력하고 builder main은 toString 결과를 report에 받아 출력합니다.

  • String 출력: title=Object
  • builder 반환: report는 게시판 헤더와 object 40m·immutable 50m·string 45m의 세 줄
  • builder 출력: main이 report를 출력, 입력 순서 유지
  • builder 상태: toString 이후에도 가변, 자동 동결 아님
  • 버린 반환값과 채택한 반환값의 관찰 차이를 보여 주며 두 main 사이에 실행 연결은 없습니다.

용량과 재사용 및 동시성

예상 결과가 매우 크다면 생성자에 초기 용량을 줄 수 있지만 실제 측정 없이 정확한 수치를 맞추는 데 집착할 필요는 없습니다.

builder는 용량이 부족하면 자동 확장됩니다.

한 요청에서 만든 builder를 static 필드로 재사용하면 이전 내용이 섞이거나 동시 호출이 충돌합니다.

setLength(0)으로 비우는 방식도 예외 경로에서 초기화를 놓치기 쉽습니다.

짧게 생성하고 버리는 객체의 비용은 JVM이 잘 최적화하므로 명확한 지역 소유권을 우선합니다.

StringBuffer는 메서드 동기화를 제공하지만 현대 코드에서는 외부 동시성 설계와 지역 StringBuilder가 일반적입니다.

여러 스레드가 하나의 텍스트 결과를 협력해 만든다면 각자 부분 문자열을 만들고 안전한 경계에서 결합하는 구조가 더 단순할 수 있습니다.

성능 비교를 해석할 때 지킬 조건

한 번 실행한 currentTimeMillis 차이만으로 API 우열을 결론 내리지 않습니다.

JVM은 시작 직후 클래스 로딩과 JIT 컴파일을 수행하고, 사용되지 않는 결과는 최적화될 수 있습니다.

입력 크기와 반복 횟수를 실제 서비스와 비슷하게 잡고 결과를 소비하며 충분히 예열한 벤치마크가 필요합니다.

전문 측정에는 JMH 같은 도구가 적합합니다.

대부분의 게시글 화면에서 항목 수십 개를 조립하는 비용은 데이터베이스나 네트워크 시간보다 작습니다.

성능 수치가 사용자 지연에 영향을 주는지 프로파일링하고, 개선 뒤에도 결과 문자열이 같은지 확인합니다.

StringBuilder를 선택할 때는 “항상 빠르다”는 주문이 아니라 반복 누적이라는 구조적 신호와 측정 근거를 함께 사용합니다.


문자열 누적 방식의 선택

  • 리터럴·변수 몇 개의 짧은 결합: + 또는 formatted
  • 반복문에서 항목 수가 늘어나는 누적: StringBuilder
  • 구분자로 목록 결합: String.join 또는 스트림 joining
  • 읽기 전용 API 반환: String
  • 문자열 조립 중간 상태: 메서드 지역 StringBuilder
  • 지속적 문서 편집: 전용 자료구조나 편집 모델 검토

StringBuilder를 반환해 호출자가 계속 수정하게 하면 내부 표현과 수명 관리가 새어 나갑니다.

필요한 결과가 문자 값이면 String으로 닫습니다.


연습 문제

태그 배열을 [java | object | string] 형식으로 만드세요.

첫 항목 앞과 마지막 항목 뒤에 구분자가 생기지 않아야 하며 빈 배열은 []를 반환합니다.

해설 보기
src/TagBuilderExercise.java
public final class TagBuilderExercise {
    public static void main(String[] args) {
        System.out.println(formatTags(new String[]{"java", "object", "string"}));
        System.out.println(formatTags(new String[]{}));
    }

    private static String formatTags(String[] tags) {
        StringBuilder result = new StringBuilder("[");
        for (int index = 0; index < tags.length; index++) {
            if (index > 0) result.append(" | ");
            result.append(tags[index]);
        }
        return result.append(']').toString();
    }
}
[java | object | string]
[]

인덱스가 0보다 클 때만 구분자를 먼저 붙이는 방식은 끝 구분자를 제거하는 후처리를 피합니다.

이 요구만 있다면 "[" + String.join(" | ", tags) + "]"도 더 간단한 해답입니다.