자바에서는 문자열을 위한
String이라는 클래스를 별도로 제공합니다.
String 클래스에는 문자열과 관련된 작업을 할 때 유용하게 사용할 수 있는 다양한 메서드가 포함되어 있습니다. 이러한 String 클래스는 java.lang 패키지에 포함되어 제공됩니다.
String 인스턴스는 한 번 생성되면 그 값을 읽기만 할 수 있고, 변경할 수는 없습니다. 이러한 객체를 자바에서는 불변 객체(immutable object)라고 합니다. 예를 들어, 자바에서 덧셈(+) 연산자를 이용하여 문자열 결합을 수행하면, 기존 문자열의 내용이 변경되는 것이 아니라 내용이 합쳐진 새로운 String 인스턴스가 생성되는 것입니다.
자바에서는 문자열을 다룰 때 String 클래스를 사용합니다.
자바의 String 클래스는 불변 객체(Immutable Object)이며, String Pool을 이용한 최적화 기능을 제공합니다.
자바에서 객체의 상태 변경 가능 여부에 따라 객체를 가변 객체(Mutable Object)와 불변 객체(Immutable Object)로 나눌 수 있습니다.
setter 메서드를 통해 값 변경 가능Side Effect(부작용)란 주된 작업 외에 추가적인 부수적 효과를 일으키는 것으로, 프로그램의 특정 부분에서 발생한 변경이 의도치 않게 다른 부분에 영향을 미치는 경우를 의미합니다.
쉽게 말하면, 객체를 공유하는 여러 변수 중 하나가 값을 변경하면, 다른 변수에도 영향을 미치는 현상입니다.
CopyByReference(참조에 의한 복사) 때문에 발생하며, 예상치 못한 버그를 유발할 가능성이 있습니다.
class Person {
String name;
Person(String name) {
this.name = name;
}
void setName(String name) {
this.name = name;
}
}
public class MutableExample {
public static void main(String[] args) {
Person p1 = new Person("홍길동");
Person p2 = p1; // p2는 p1과 같은 객체를 참조 (Copy by Reference)
p2.setName("안중근"); // p2에서 이름을 변경
System.out.println(p1.name); // "안중근" (p1도 영향을 받음)
System.out.println(p2.name); // "안중근"
}
}
불변 객체란 객체 내부의 값, 필드, 멤버 변수가 변하지 않는 객체를 의미합니다. 클래스 작성 시 인스턴스 변수 선언에 final 예약어를 붙여주고, setter를 작성하지 않는 방법으로 불변 객체를 생성합니다.
final 키워드를 사용하여 필드를 변경할 수 없도록 함setter 메서드를 만들지 않음String 클래스는 불변 객체로 설계되어 있습니다.
final class ImmutablePerson {
private final String name; // 필드를 final로 선언
public ImmutablePerson(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
final 이므로 한 번 설정되면 변경할 수 없습니다.setter 메서드가 없어서 객체가 한 번 생성되면 상태가 바뀌지 않습니다.public class ImmutableExample {
public static void main(String[] args) {
ImmutablePerson p1 = new ImmutablePerson("홍길동");
// p1.setName("안중근"); // 불가능 (setter 없음)
}
}

자바에서 String은 특별한 메모리 관리 기법을 사용하는데, 이를 "String Pool” 이라고 합니다.
이는 자주 사용되는 문자열을 공유하여 메모리를 절약하고 성능을 향상시키는 역할을 합니다.
new String("hello")를 사용하면 String Pool을 사용하지 않고, Heap에 새로운 객체를 생성자바의 String은 특별한 클래스로, 문자열 리터럴이 동일하면 같은 객체를 참조하도록 동작합니다.
하지만new String("hello")로 생성하면 항상 새로운 객체가 생성됩니다.
문자열 리터럴(String Pool 사용)
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == 2); // true
new String()을 사용하면 Heap에 새로운 객체가 생성됨
String s3 = new String("hello");
String s4 = new String("hello");
Systme.out.println(s3 == s4); // false
new String("hello")를 사용하면 Heap 메모리에 새로운 String 객체가 생성됨intern() 메서드를 사용하면 Heap에 생성된 String 객체를 String Pool에 추가할 수 있습니다.
String s5 = new String("world").intern();
String s6 = "world";
System.out.println(s5 == s6); // true
new String("world") → Heap에 새로운 String 객체 생성String Pool은 예전에는 Method Area(메서드 영역)에 있었지만, 현재는 Heap에 포함됩니다.
JDK 7부터는 String Pool이 Heap 영역으로 이동했습니다.
String Pool이 Heap으로 이동한 이유는 다음과 같습니다.
OutOfMemoryError 발생 가능자바에서는 문자열을 비교할 때 ==와 equals()를 사용합니다.
==는 문자열의 “내용”이 아니라, 객체의 “참조값(메모리 주소)“을 비교합니다.
두 개의 문자열이 "같은 객체"인지 비교합니다. 메모리 주소(참조값)를 비교하여, 동일한 객체일 경우 true를 반환합니다.
public class StringComparison {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
System.out.println(s1 == s2); // true (String Pool에서 공유)
System.out.println(s1 == s3); // false (s3는 Heap에서 생성된 객체)
}
}
s3 = new String("hello")를 사용하면 Heap에 새로운 객체가 생성되므로 s1 == s3는 false
equals()는 문자열 값(내용)을 비교할 때 사용합니다.
문자열의 "내용"을 비교하여 같으면 true를 반환합니다. 문자열이 동일한 값이면 true, 다르면 false를 반환합니다.
public class StringComparison {
public static void main(String[] args) {
String s1 = "hello";
String s2 = new String("hello");
System.out.println(s1.equals(s2)); // true (문자열 내용이 같음)
}
}
equals()는 문자열의 내용을 비교하므로, s1.equals(s2)는 true| 비교 방식 | 설명 | 예제 결과 |
|---|---|---|
== (참조 비교) | 두 객체가 같은 메모리 주소를 가리키는지 확인 | "hello" == "hello" → true (String Pool 공유) |
equals() (내용 비교) | 문자열의 "값"을 비교 | "hello".equals(new String("hello")) → true |
String은 불변 객체이므로 문자열을 자주 변경하면 새로운 객체가 계속 생성되어 비효율적입니다.
자바에서 문자열을 자주 변경해야 하는 경우, String 대신 StringBuilder 또는 StringBuffer를 사용하는 것이 효율적입니다.
| 클래스 | 변경 가능 여부 | 스레드 안전(Thread-Safe) | 성능 |
|---|---|---|---|
| String | 불변 | 안전 | 느림 (새 객체 생성) |
| StringBuilder | 가변 | 비안전 | 빠름 |
| StringBuffer | 가변 | 안전 | 느림 (synchronized 사용) |
String은 변경할 수 없고, 새로운 객체가 계속 생성됩니다.
public class StringExample {
public static void main(String[] args) {
String str = "Hello"; // String Pool에 저장됨
Str += " World"; // 새로운 문자열 객체 생성됨 (Heap)
System.out.println(str); // "Hello World"
}
}
즉, String은 한 번 생성되면 내부 값을 변경할 수 없고, 새로운 객체를 생성해야 합니다. 문자열을 자주 변경하는 경우 String은 성능이 느려질 수 있습니다.
StringBuilder는 기존 객체를 수정하기 때문에 String보다 성능이 좋습니다.
public class StringBuilderExample {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 기존 객체에 " World" 추가
System.out.println(sb); // "Hello World"
}
}
.append("World")를 호출하면 기존 객체를 직접 수정하여 "Hello World"가 됨즉, StringBuilder는 문자열을 여러 번 수정할 때 효율적입니다. 단일 스레드 환경에서 가장 빠르게 문자열을 조작할 수 있습니다.
StringBuffer는 synchronized(동기화)를 사용하여 멀티스레드 환경에서도 안전하게 문자열을 수정할 수 있습니다.
public class StringBufferExample {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");
sb.append(" World"); // 동기화된 메서드 (멀티스레드 환경에서도 안전)
System.out.println(sb); // "Hello World"
}
}
.append(" World")를 호출하면 기존 객체를 직접 수정하여 "Hello World"가 됨즉, StringBuffer는 멀티스레드 환경에서도 안전하지만, 동기화 때문에 StringBuilder보다 속도가 느립니다.
스레드 안전이란 멀티스레드 환경에서 동시에 실행할 때 데이터의 일관성이 보장되는 것입니다.
StringBuffer는 동기화(synchronized)를 사용하여 멀티스레드 환경에서도 안전하게 사용 가능하지만, StringBuilder는 단일 스레드에서만 안전하며, 멀티스레드 환경에서는 충돌이 발생할 수 있습니다.
StringBuffer는 synchronized 키워드를 사용하여 동기화를 적용합니다.
한 번에 하나의 스레드만 StringBuffer 메서드를 실행할 수 있도록 제한합니다.
public class StringBufferExample {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");
sb.append(" World"); // 동기화된 메서드 (스레드 안전)
System.out.println(sb); // "Hello World"
}
}
append(), insert(), delete() 같은 메서드는 내부적으로 synchronized 처리되어 있음synchronized를 따로 명시하지 않아도 스레드 안전(Thread-Safe)이 보장됨