본문 바로가기

Java

String은 왜 불변 객체로 만들었을까?

728x90

Java에서 String은 불변 객체다.

이전글 : https://riverblue.tistory.com/42?category=929398

 

Java String, StringBuilder, StringBuffer

자바에서 문자열 클래스는 대표적으로 String, StringBuilder, StringBuffer가 있다. 사실 String밖에 다루지 않아봐서 잘 몰랐는데 좋은 경험을 하게 되어서 나머지 두 클래스도 다뤄야 겠다고 생각했다. St

riverblue.tistory.com

 

근데 한번도 왜 불변으로 만들었는지, 불변이 뭐가 좋은지 생각해본적이 없다.

그래서 이유를 좀 찾아보게 되었고 나름대로 정리한 생각을 기록해본다.

 

참고 : https://www.baeldung.com/java-string-immutable

 

Why String is Immutable in Java? | Baeldung

Explore why Strings in the Java language are immutable.

www.baeldung.com

 

위 사이트를 보면 대략 4가지로 생각된다.

  • String Pool
  • 동기화
  • HashCode Caching
  • 보안

 

 

1. String Pool

처음 안 사실인데, 메모리의 힙 영역에 String Pool이 따로 있다고 한다. 참고한 사이트에선 Java String Pool is the special memory region where Strings are stored by the JVM 라고 표현하고 있다.

 

String s1 = "Hello World";

위 같은 String 객체가 있을때, Hello World를 String 리터럴 값이라고 한다. 이 리터럴 값이 String Pool에 저장되고, 해당 값을 또 선언한 경우 같은 주소값을 참조한다고 한다. 코드로 보면 이해가 편하다.

 

String s1 = "hello";
String s2 = "hello";

System.out.println(s1 == s2);

결과는 true.

사실 난 당연히 false가 나올줄 알았다. String은 equals로 비교해야하니까. 아래 그림을 보면 이해가 쉽다.

 

출처 : https://www.baeldung.com/java-string-immutable

 

아마도 성능을 위해 이러한 String pool이 존재하는듯 하다.

 

String s3 = new String("world");
String s4 = new String("world");

System.out.println(s3 == s4);

참고로 이건 당연히 false다.

 

2. 동기화

멀티 스레드 환경에서 안전하다는 의미다. 불변이기 때문에 메모리에 저장된 값이 바뀔일 없다.

그래서 당연히 Thread Safe한 효과를 얻을 수 있다.

 

3. HashCode Caching

String은 hashCode() 메서드가 있다. 사실 개인적으로 직접 호출한 적이 없어서 몰랐는데 HashMap, HashTable, HashSet 등 아주 여기저기 자주 쓰이는 메서드라고 한다. 그래서 String을 좀 살펴보면

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
    
    .....

	public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

 

private int hash로 필드 변수가 선언되어 있고, 해시코드 캐싱을 위한 값이라고 주석처리 되어있는걸 볼 수 있다.

hashCode()는 h가 0인 경우 h에 값을 넣기 때문에 최초에만 해시값을 넣는걸 알 수 있다.

String은 불변이기 때문에 값이 바뀔일없어서 최초에 초기화된 해시값이 변하지 않을것이다. 때문에 필요할때 마다 이미 정의된 hash값으로 빠르게 사용이 되는 것이다.

 

 

4. 보안

솔직히 이 부분은 추상적으로 이해했다.

먼저 보안이라고 나타낸이유는 아무래도 중요한 데이터는 String 형태로 많이 저장하기 때문에 보안이라고 표시한듯 하다. (아이디, 암호, url 등)

 

참고한 사이트의 코드를 먼저 보면

void criticalMethod(String userName) {
    // perform security checks
    if (!isAlphaNumeric(userName)) {
        throw new SecurityException(); 
    }
	
    // 여기 있을때 username이 바뀐다면?
    
    // do some secondary tasks
    initializeDatabase();
	
    // 여기 있을때 username이 바뀐다면?
    
    // critical task
    connection.executeUpdate("UPDATE Customers SET Status = 'Active' " +
      " WHERE UserName = '" + userName + "'");
}

위에서 주석으로 표시한 타이밍에 다른 스레드에 의해 userName값이 바뀌게 되면 그 이후의 코드에 문제가 생길 수 있다는 점이다. => initializeDatabase()가 안되거나 executeUpdate에 다른 쿼리가 되거나

 

특히 1번의 String Pool이 있기 때문에 같은 리터럴 값을 참조하는 String 객체가 여러개일수도 있다.

 

userName = "pooreun";

String some = "pooreun";

some = "zzzzz";

만약 가변이라면 some = "zzzzz"하는 순간 String Pool에 있던 "pooreun"도 "zzzzz"로 바뀌기 때문에 문제가 발생할 수 있다는 뜻이다.

 

728x90