String

Rookedsysc·2024년 12월 2일

JVM

목록 보기
2/3

String

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 vs StringBuffer

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;
    }

String Constant Pool

Java에서 문자열 리터럴을 저장하는 독립된 영역String Constant Pool이라고 부릅니다. 일반적으로 GC 대상이 되지 않지만 문자열 참조 대상이 없는 경우 선택적으로 GC 대상이 되기도 합니다.
Constant Pool이라서 Runtime Constant Pool에 있다고 오해하기 쉽지만 String Constant PoolHeap Area에 존재한다.

new Keyword를 사용하지 않고 ""로 할당한 문자열을 리터럴이라고 한다.

String Constant Pool에 문자열이 저장되는 조건

  1. 소스코드에 리터럴 문자열을 선언한 경우
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
  1. String.intern() 메서드를 실행한 경우

사용비용이 비싸고 느리기 때문에 잘 사용하지 않지만 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를 반환하도록 구현되었기 때문이다.

  • hash: 캐시된 해시코드 값을 저장하는 필드
  • hashIsZero: 해시코드가 0인 경우를 특별히 표시하는 boolean 필드
  • value: 실제 문자열 내용을 저장하는 byte 배열

Reference

0개의 댓글