평범했던 평일 오후, 오픈 톡방에서 한 사람이 Android Intent 이동 부분에서 발생한 오류 부분을 알려달려고 물어 봤다.
final String val = "길찾기"
if ( val == textView.getText().toString()) {
// Intent로 다른 엑티비티로 이동
}
보자마자 String 객체를 == 연산으로 비교한 것이 잘못이라고 생각했지만 큰 오산이었다. String의 메모리 할당 구조에 대해서 잘 알지 못하여 발생한 착각이었다.
Java String 참조형 비교
int a1 = 1;
int a2 = 1;
String b1 = new String("Hello world");
Integer b2 = new String("Hello world");
System.out.println(a1 == a2);
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
//true
//false
//true
참조형 변수, 객체가 존재하는 Java에서 == 연산
은 주소 값을 비교하는 연산이다. 그래서 자연스럽게 객체의 값을 비교하기 위해 eqauls
함수를 호출해야 한다고 암기를 했었다. 그런데 객체의 선언에서 literal
이 사용되어 진다면 상황이 달라진다.
Java String은 immutable
String s1 = "Hello world";
s1 += " Test";
String 객체는 다른 객체와 달리 immutable
특성을 가진다. String의 어떤 함수를 사용해도 문자열을 직접 수정할 수 없다.위 코드에서 s1 변수는 +=
연산을 수행하면 기존 참조하던 값을 버리고 "Hello world Test"
라는 새로운 값을 메모리에 저장하여 참조한다.
반복문으로 +
연산으로 문자열을 생성한다고 하면 반복 횟수만큼 메모리가 생성되고 소멸되는 것이다. 이처럼 비효율적인 String 객체의 메모리 저장 방식을 개선하기 위해 Java는 String pool
을 사용하고 있다.
Java String Pool
String s1 = "Hello world" // (1)
String s2 = "Hello world" // (2)
String s3 = new String("Hello world"); //(3)
System.out.println(s1 == s2);
System.out.println(s1 == s3);
//true
//false
Java에서는 기본형의 값들은 컴파일 단계에서 constant pool
에 저장되어 같은 주소를 가리키게 된다. 기본형의 값들은 변하지 않는 상수이기 때문이다. String pool
이 가능한 이유도 immutable
특성 덕분이다.
(1) 에서 Hello world
는 Heap
메모리의 String pool
영역에 저장이 되고 s1 변수가 그 주소를 가리킨다.
(2) 에서도 Hello world
를 String pool
에 저장하려고 보았더니 이미 존재하는 것을 확인하고 s2 변수는 s1 변수랑 같은 주소를 가리키게 된다.
(3) 은 실질적인 String 값은 동일하지만 new
키워드로 새로운 객체 생성을 명시하였기 때문에 Heap
메모리에 저장이 되어 결과적으로 다른 주소를 가르키게 된다.
정리하며
발생한 오류 부분은 ==
연산 때문이 아니었다. 문자열이 이미 상수로 선언되어 있기 때문에 ==
연산으로도 개발자가 생각했던 값 비교가 된다는 것! String 객체의 비교를 꼭 equals
함수로 해야하는 것은 아니다라는 사실을 알게 되었다. equlas
함수는 ==
연산에 비해 느리다고 하기 때문에 상황에 따라 알맞게 쓰면 된다.
참고
'Language > Java' 카테고리의 다른 글
[Java] Thread 강제 종료 시키는 방법 (0) | 2021.05.11 |
---|---|
Java의 Singleton (0) | 2020.06.01 |