Java에서 String을 사용할 때 주의할 점이 있다.
내가 개발을 할 때, 상수로 처리할려는 변수는 전역변수로 처리하는 습관?이 있었는데 이는 메모리 낭비가 될 수 있다.
String은 두 가지 생성 방식이 있고 각각의 차이점이 존재한다.
1. new 연산자를 이용한 방식
2. 리터럴을 이용한 방식
두 가지 방식에는 큰 차이점이 있다.
new를 통해 String을 생성하면 Heap 영역에 존재하게 되고
리터럴을 이용할 경우 string constant pool이라는 영역에 존재하게 된다.
new 연산자를 이용하여 String을 선언하는 방법과 리터럴을 이용한 방식에 차이가 있다고 했다.
그럼 두 가지 방식에 있어서 사용에 있어 어떤 문제가 있을 수 있을까?
public class StringMemory {
public static void main(String... args) {
String literal = "loper";
String object = new String("loper");
System.out.println(literal == object);
System.out.println(literal.equals(object));
}
}
두 개의 결과값은 어떻게 나올까?
분명 동일한 “loper”라는 문자열을 선언한 String 객체이다.
== 연산의 결과는 false이고 equals 수행 결과는 true이다.
equals는 문자열을 비교하기 때문에 같은 문자열에 대해서 true가 맞다.
하지만 == 연산자의 경우 객체의 주소값을 비교하기 때문에 일반 객체처럼 Heap 영역에 생성된 String 객체와 리터럴을 이용해 string constant pool에 저장된 String 객체의 주소값은 다를 수밖에 없다.
왜 이런 결과가 나오는 것일까?
동작 방식에 대한 이해가 필요하다.
String을 리터럴로 선언할 경우 내부적으로 String의 intern() 메서드가 호출되게 된다. intern() 메서드는 주어진 문자열이 string constant pool에 존재하는지 검색하고 있다면 그 주소값을 반환하고 없다면 string constant pool에 넣고 새로운 주소값을 반환하게 된다.
public class StringMemoryIntern {
public static void main(String... args) {
String literal = "loper";
String object = new String("loper");
String intern = object.intern();
System.out.println(literal == object); // false
System.out.println(literal.equals(object)); // true
System.out.println(literal == intern); // true
System.out.println(literal.equals(intern)); // true
}
}
기존에 new를 통해 생성된 String 객체와 리터럴로 생성된 String 객체를 == 연산하였을 경우 false를 반환하였지만 new를 통해 생성된 String 객체의 intern() 메서드를 호출하여 새로운 String 객체에 대입하였을 경우 리터럴로 생성된 String 객체와 == 연산시 true를 반환하게 된다.
위에서 설명하였듯이 리터럴로 “loper”라는 문자열이 string constant pool에 저장되었고 intern() 메서드를 호출하면서 string constant pool에 “loper”라는 문자열을 검색하게 되고 이미 저장한 “loper” 문자열을 발견할 테니 동일한 주소값을 반환하게 되므로 true가 성립되게 되는 것이다.
즉, String 타입으로 변수를 정의할 때 상수인 경우 무조건 전역변수로 정의하고
리터럴을 이용한 방식을 사용하는 것은 메모리 낭비가 될 수 있다.
이미 리터럴 변수로 정의된 건은 string constant pool 영역에 저장이 되어있고
또 다시 동일 변수를 정의하고자 할 때, intern() 메소드를 통해 string constant pool 영역에 있다면 바로 가져오기 때문이다.
굳이 여러 메소드에서 사용하는 전역변수가 아니라면 굳이 전역으로 정의해서 메모리 낭비를 할 필요가 없다. 리터럴 방식으로 지역변수로 처리하는 게 더 효율적이다.