Java에서 문자열 결합 시 많이 사용하는 + 연산은 직관적이고 간단하지만, 성능 측면에서 적절하지 않은 경우가 있습니다. 이번 글에서는 문자열 결합의 효율적인 방법과 이유에 대해 설명하겠습니다.
Java에서의 + 연산
Java의 String은 immutable(불변)한 객체입니다. 따라서 String 간의 + 연산은 기존 객체를 수정하지 않고, 새로운 String 객체를 생성하게 됩니다. 이는 다음과 같은 코드에서 성능 문제가 발생할 수 있습니다.
// 비효율적인 코드 예시
String res = "";
for (int i = 0; i < 100; ++i) {
res += i; // 매번 새로운 String 객체 생성
}
위 코드는 for 문이 실행될 때마다 새로운 String 객체를 생성하여 성능 저하를 유발합니다.
+ 연산이 효율적인 경우
단일 라인의 +연산에서는 Java 컴파일러가 내부적으로 최적화합니다.
String res = "Hello" + " " + "World" + "!";
컴파일러가 위 코드를 다음과 같이 변환할 수 있습니다.
String res = new StringBuilder()
.append("Hello")
.append(" ")
.append("World")
.append("!")
.toString();
즉, 단일 라인의 경우에는 + 연산도 최적화되어 StringBuilder를 사용하는 것과 동일한 효과를 얻을 수 있습니다.
이는 JLS 15.18.1에 명시되어 있습니다.
https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.18.1
Chapter 15. Expressions
docs.oracle.com
To increase the performance of repeated string concatenation, a Java compiler may use the StringBuffer class or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression.
반복문에서는 왜 + 연산이 비효율적인가?
단일 라인의 + 연산은 최적화가 되지만, 반복문에서는 결국 매번 새로운 문자열 객체를 생성합니다.
따라서, 문자열을 여러 번 결합하는 경우 mutable한 문자열 결합 클래스를 사용하는 것이 좋습니다.
StringBuilder
StringBuilder는 수정 가능한 가변 문자열 클래스입니다.
객체를 한 번 생성한 후 append() 메서드를 사용해 문자열을 효율적으로 결합할 수 있습니다.
append()는 다양한 객체에 대한 명시적 형 변환을 지원합니다. 단, 내부적으로는 객체의 toString()을 호출해 문자열을 변환합니다.
public class Main {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i < 10; ++i) {
sb.append(i).append('\n');
}
System.out.print(sb); // 연결한 문자열이 출력된다.
}
}
멀티스레드 환경에서는 StringBuffer
StringBuilder는 Non-Synchronized 클래스입니다. 멀티스레드 환경에서 동기화 문제가 발생할 수 있기 때문에, 이 경우에는 StringBuffer를 사용해 안전하게 문자열 결합을 수행할 수 있습니다.
StringJoiner
StringJoiner는 구분자(Delimiter) 기반 문자열 결합 클래스입니다.
구분자를 자동으로 추가해주기 때문에 StringBuilder보다 코드가 더 간결하고 명확해집니다.
add() 는 CharSequence 만을 인자로 받는다.
public class Main {
public static void main(String[] args) {
StringJoiner sj = new StringJoiner("\n");
for (int i = 1; i < 10; ++i) {
sj.add(String.valueof(i)); // 구분자를 추가하지 않아도 된다.
}
System.out.print(sj);
}
}
다음과 같이 구분자 외에도 접미사/접두사를 지정할 수 있습니다.
public class Main {
public static void main(String[] args) {
StringJoiner joiner = new StringJoiner(", ", "[", "]");
joiner.add("Apple").add("Banana").add("Orange");
System.out.println(joiner.toString()); // 출력: [Apple, Banana, Orange]
}
}
결론
+ 연산은 단일 라인에서는 효율적이지만, 반복문 등 복잡한 상황에서는 성능 저하의 원인이 됩니다. 이러한 경우에는 StringBuilder나 StringJoiner와 같은 클래스를 사용해 성능을 개선할 수 있습니다.
'Programming Language > Java' 카테고리의 다른 글
[Java] List.of()로 생성된 불변 리스트와 컬렉션 초기화 (0) | 2025.02.13 |
---|---|
[Java] 람다 표현식의 반환 타입 추론 (0) | 2025.02.13 |
[Java] Java에서의 Thread와 Thread Pool (1) | 2025.02.08 |
[Java] effectively final (0) | 2025.01.06 |
[Java] Double.MIN_VALUE는 음수가 아니다. (1) | 2024.11.20 |