자바 개발 중 정수형 객체를 비교할 때 상식적으로 이해하기 어려운 결과가 발생할 때가 있습니다. 다음의 코드 예제를 살펴보겠습니다.
Integer a1 = Integer.valueOf("127");
Integer a2 = Integer.valueOf("127");
System.out.println(a1 == a2); // 결과: true
Integer b1 = Integer.valueOf("128");
Integer b2 = Integer.valueOf("128");
System.out.println(b1 == b2); // 결과: false
int c1 = Integer.parseInt("128");
Integer c2 = Integer.valueOf("128");
System.out.println(c1 == c2); // 결과: true
첫 번째 비교는
true가 나오지만, 값만 128로 바꾼 두 번째 비교는
false가 출력됩니다. 또한 세 번째 비교는 다시
true를 반환합니다. 이러한 차이가 발생하는 기술적 이유를 정리합니다.
1. Integer Cache의 역할
Integer.valueOf(String) 메서드는 단순히 문자열을 정수로 바꾸는 것 이상의 동작을 수행합니다. 내부적으로
Integer.valueOf(int)를 호출하며, 이때
Integer Cache라고 불리는 메커니즘이 작동합니다.
자바의
Integer 클래스는 성능 최적화를 위해 -128에서 127 사이의 값을 가진
Integer 객체들을 미리 생성하여 메모리에 캐싱해 둡니다. 따라서
valueOf("127")을 호출하면 새로운 객체를 생성하는 대신 이미 메모리에 존재하는 캐시 객체의 참조를 반환합니다.
결과적으로
a1과
a2는 동일한 메모리 주소를 가리키게 되어
== 연산 시
true가 반환됩니다. 반면 128은 캐시 범위를 벗어나기 때문에 매번 새로운 객체가 생성되어 주소값이 달라지므로
false가 됩니다.
2. parseInt()와 valueOf()의 차이
세 번째 케이스인
Integer.parseInt("128") == Integer.valueOf("128")이
true인 이유는 반환 타입의 차이에 있습니다.
Integer.parseInt(): 기본 자료형인 int를 반환합니다.
Integer.valueOf(): 래퍼 클래스인 Integer 객체를 반환합니다.
기본 자료형(primitive)과 객체(wrapper)를
== 연산자로 비교하면 자바 컴파일러는 자동으로
언박싱(Unboxing)을 수행합니다. 즉, 오른쪽의
Integer 객체에서
int 값을 꺼내어 비교하게 됩니다. 결과적으로
128 == 128이라는 값 비교가 이루어지므로
true가 출력되는 것입니다.
3. 권장되는 비교 방법
객체 간의 논리적인 값을 비교하고 싶다면 주소값을 비교하는
== 연산자 대신
equals() 메서드를 사용해야 합니다.
equals()는 캐시 여부와 상관없이 객체가 들고 있는 내부의
int 값을 비교하므로 안전합니다.
Integer x = Integer.valueOf("128");
Integer y = Integer.valueOf("128");
System.out.println(x.equals(y)); // 항상 true
참고로, Integer Cache의 상한값(기본 127)은 JVM 시작 옵션인
-XX:AutoBoxCacheMax=<size> 설정을 통해 변경할 수 있지만, 하한값은 -128로 고정되어 있습니다.