본문 바로가기
Programming/Java

String 클래스의 특징(Java 1.8)

by HBGB 2021. 7. 17.

2가지의 생성방식 & 생성되는 메모리 영역

String strA = new String("AAA");     // (1)
String strB = "AAA";                 // (2)

(1) New 연산자를 사용하여 Heap 영역에 생성하는 방식

(2) 문자열 리터럴을 사용하여 string constant pool 에 생성하는 방식

참고

Java 1.7부터 Perm영역에 있던 constant pool이 Heap 영역으로 옮겨졌고,

Java 1.8부터는 Perm영역이 사라지고, 대신에 Native Memory영역에 Metaspace 영역이 추가되었다.

 

 

 

 

String은 변경 불가능한(immutable) 객체

public final class String implements java.io.Serializable, Comparable {

    private final char[] value;
    ...
}

생성자의 매개변수로 입력받는 문자열은 인스턴스 변수(value)에 char형 배열(char[])의 레퍼런스가 저장된다.

한번 생성된 String 인스턴스가 가리키는 문자열은 읽어올 수만 있고, 변경될 수는 없다.

 

 

문자열 결합 == 새로운 문자열 객체

String a = "a";
String b = "b";
a = a + b; // 새로운 문자열 리터럴 == "ab"
String a = new String("a");
String b = new String("b");
a = a + b; // 새로운 String 인스턴스 == new String("ab")
  • 기존 문자열에 더해지는 것이 아니라, 매 연산시마다 새로운 문자열이 만들어진다.
  • 메모리공간을 비효율적으로 사용하는 것
  • 문자열 결합이 필요하면 StringBuilder / StringBuffer 를 쓰는 것이 적절하다.

 

 

new로 생성된 String 객체는 모두 다르다

String heapStr1 = new String("abc");
String heapStr2 = new String("abc");

System.out.println(heapStr1 == heapStr2); // false
System.out.println(heapStr1 == "abc");    // false
  • heapStr1과 heapStr2은 문자열이 모두 "abc"이지만, 서로 다른 별개의 객체이다.
  • heap영역에 생성된 객체이므로, constant영역에 생성된 문자열 리터럴과도 다르다.

 

 

문자열 리터럴은 모두 같은 객체를 참조한다

String s1 = "AAA";
String s2 = "AAA";
String s3 = "AAA";

System.out.println(s1 == s2); // true
System.out.println(s2 == s3); // true
  • 참조 변수 s1, s2, s3는 모두 constant pool 에서 "AAA"라는 문자열을 담고 있는 String 객체 하나를 참조한다.
문자열 리터럴의 생성과정
  1. 자바 소스파일에 문자열 리터럴이 포함되는 경우
  2. 컴파일 시에 같은 내용의 문자열 리터럴은 클래스 파일한번씩만 저장된다.
  3. 클래스 파일에는 소스파일에 포함된 모든 리터럴의 목록이 있다.
  4. 해당 클래스 파일이 클래스 로더에 의해 메모리에 올라갈 때, 이 목록에 있는 리터럴들이 JVM의 '상수 저장소(constant pool)'에 저장된다.

 

 

intern() 메소드와 constant pool

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
  ...

  /**
   * Returns a canonical representation for the string object.
   * <p>
   * A pool of strings, initially empty, is maintained privately by the
   * class {@code String}.
   * <p>
   * When the intern method is invoked, if the pool already contains a
   * string equal to this {@code String} object as determined by
   * the {@link #equals(Object)} method, then the string from the pool is
   * returned. Otherwise, this {@code String} object is added to the
   * pool and a reference to this {@code String} object is returned.
   * <p>
   * It follows that for any two strings {@code s} and {@code t},
   * {@code s.intern() == t.intern()} is {@code true}
   * if and only if {@code s.equals(t)} is {@code true}.
   * <p>
   * All literal strings and string-valued constant expressions are
   * interned. String literals are defined in section 3.10.5 of the
   * <cite>The Java&trade; Language Specification</cite>.
   *
   * @return  a string that has the same contents as this string, but is
   *          guaranteed to be from a pool of unique strings.
   */
  public native String intern();
}

위 코드는 String 클래스의 intern 메소드에 달려있는 주석이고, 이를 번역해보면 다음과 같다.

  • intern 메소드는 string 객체의 정식 표현을 반환한다. string pool은 처음에는 비어있으며, String 클래스에 의해서 따로 유지된다.
  • intern 메소드가 호출되었을 때, equals() 메소드 실행 결과 이 String 객체와 동일한 문자열이 string pool 에 있으면, pool에 있는 객체의 문자열이 반환된다. 동일한 문자열이 pool에 없다면, 이 String 객체가 string pool에 추가되고, 해당 객체의 레퍼런스가 반환된다.
  • 이는 s.equals(t) == true 일때만 s.intern() == t.intern() 이 true임을 뜻한다.
  • 모든 문자열 리터럴과 문자값을 가진 constant 표현은 'interned' 된다.
  • ​ (의역하면 'string pool에 가둔다' 쯤이 될 것 같다)
  • @반환 : intern 메소드는 이 String 객체와 같은 내용의 문자열을 반환하지만, 이것은 string pool에 있는 유니크한 문자열인 것이 보증된다.

 

이를 코드로 확인하면 다음과 같다.

String heapStr1 = new String("abc");
String heapStr2 = new String("abc");
String poolStr1 = heapStr1.intern();
String poolStr2 = heapStr2.intern();

System.out.println(heapStr1 == poolStr1); // false
System.out.println(heapStr1 == "abc");    // false
System.out.println(poolStr1 == poolStr2); // true
System.out.println(poolStr1 == "abc");    // true
  • heapStr1의 intern() 결과인 poolStr1이 poolStr2, "abc"와 동일한 레퍼런스를 참조하고 있다는 점을 알 수 있다.

 

 

정리하면,

  • 모든 문자열 리터럴은 string constant pool에 유일하게 저장된다 == interned
  • new 로 생성된 String의 인스턴스는 heap 영역에 개별적으로 존재하지만,
  • intern() 메소드를 호출하면 string constant pool에 동일한 문자열이 등록된다.

 

 

 

+) 알아두면 유용할 것 같은 클래스 - StringJoiner -> Java 1.8부터 String 클래스의 join() 메서드가 추가됨

  • 여러 String들을 지정한 delimiter로 이어붙여서 하나의 String으로 만들어준다
  • prefix와 suffix 또한 지정가능하다
StringJoiner sj = new StringJoiner(",", "[", "]");
String[] strArr = {"aaa", "bbb", "ccc"};

for (Stirng s : strArr) 
    sj.add(s.toUpperCase());

System.out.println("출력 : " + sj.toString()); // 출력 : [AAA, BBB, CCC]

댓글