String은 불변이다.
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc {
/**
* The value is used for character storage.
*
* @implNote This field is trusted by the VM, and is a subject to
* constant folding if String instance is constant. Overwriting this
* field after construction will cause problems.
*
* Additionally, it is marked with {@link Stable} to trust the contents
* of the array. No other facility in JDK provides this functionality (yet).
* {@link Stable} is safe here, because value is never null.
*/
@Stable
private final byte[] value;
심지어 replace() 함수 조차도 불변으로 새 StringBuilder에서 toString()을 해서 원래 있던 String을 변화하는 방식이 아닌 새로운 String을 반환하는 방식으로 동작을 한다.
public String replaceFirst(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
}
public String replaceFirst(String replacement) {
if (replacement == null)
throw new NullPointerException("replacement");
reset();
if (!find())
return text.toString();
StringBuilder sb = new StringBuilder();
appendReplacement(sb, replacement);
appendTail(sb);
return sb.toString();
}
StringBuilder는 동시성을 고려하지 않은 클래스이고 StringBuffer는 내부적으로 Synchronized를 활용해서 동시성을 고려한 클래스이다. StringBuilder와 StringBuffer는 모두 AbstractStringBuilder를 상속해서 구현하고 있는데,
public final class StringBuilder
extends AbstractStringBuilder
implements Appendable, java.io.Serializable, Comparable<StringBuilder>, CharSequence
{}
public final class StringBuffer
extends AbstractStringBuilder
implements Appendable, Serializable, Comparable<StringBuffer>, CharSequence
{}
abstract sealed class AbstractStringBuilder implements Appendable, CharSequence
permits StringBuilder, StringBuffer {
/**
* The value is used for character storage.
*/
byte[] value;
}
세부구현에서 syncrhronized를 사용하고 있냐, 안하고 있냐로 갈린다.
// StringBuffer의 Append
@Override
@IntrinsicCandidate
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
// StringBuilder의 Append
@Override
@IntrinsicCandidate
public StringBuilder append(String str) {
super.append(str);
return this;
}

Java에서 문자열 리터럴을 저장하는 독립된 영역을 String Constant Pool이라고 부릅니다. 일반적으로 GC 대상이 되지 않지만 문자열 참조 대상이 없는 경우 선택적으로 GC 대상이 되기도 합니다.
Constant Pool이라서 Runtime Constant Pool에 있다고 오해하기 쉽지만 String Constant Pool은 Heap Area에 존재한다.
new Keyword를 사용하지 않고 ""로 할당한 문자열을 리터럴이라고 한다.
String name = "rookedsysc";
String name1 = "rookedsysc";
String name2 = new String("rookedsysc");
String name3 = new String("rookedsysc");
System.out.println(name == name1); // true (같은 객체를 재사용하기 때문에)
System.out.println(name == name2); // false
System.out.println(name2 == name3); // false
System.out.println(name.equals(name1) && name1.equals(name2) && name2.equals(name3)); // true
사용비용이 비싸고 느리기 때문에 잘 사용하지 않지만 intern() 메서드를 사용해도 Constant Pool에 넣을 수 있다. 하지만 어떠한 경우에도 사용하지 않는게 좋다.
public static void main(String[] args) {
String value1 = "abc";
String value2 = "abc";
String value3 = new String("abc");
String value4 = new String("abc").intern();
if(value1.equals(value2)) {
System.out.println("value1.equals(value2)"); // 출력
}
if(value1 == value2) {
System.out.println("value1 == value2"); // 출력
}
if(value1 == value3) {
System.out.println("value1 == value3"); // 출력 안됨
}
if(value1.equals(value3)) {
System.out.println("value1.equals(value3)"); // 출력
}
if(value1 == value4) {
System.out.println("value1 == value4"); // 출력
}
if(value1.hashCode() == value2.hashCode()) {
System.out.println("value1.hashCode() == value2.hashCode()"); // 출력
}
if(value1.hashCode() == value3.hashCode()) {
System.out.println("value1.hashCode() == value3.hashCode()"); // 출력
}
}
위 4번째 조건에서 Sout("value1.equals(value3)")가 출력되는 이유는 String의 equals() 메서드는 내부적으로 String의 실제 Value와 Encoding 방식을 비교하기 때문이다.
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
return (anObject instanceof String aString)
&& (!COMPACT_STRINGS || this.coder == aString.coder)
&& StringLatin1.equals(value, aString.value);
}
위 마지막 조건에서 System.out.println("value1.hashCode() == value3.hashCode()");가 출력되는 이유는 equals()가 문자열 내용이 같으면 같은 hashcode를 반환하도록 구현되었기 때문이다.