티스토리 뷰
[Java] Integer.valueOf(127) == Integer.valueOf(127)가 True인 이유, Integer 캐시
망나니개발자 2022. 11. 8. 10:00이번에는 Integer.valueOf(127) == Integer.valueOf(127)가 True인 이유에 대해서 알아보도록 하겠습니다. 이 글은 Dzone의 글을 번역 및 정리한 내용입니다.
1. 문제 상황 소개
[ 문제 상황 소개 ]
다음과 같은 테스트 코드가 있다고 할 때, 출력 결과를 예상해보도록 하자.
@Test
void compareInteger() {
Integer num1 = 128;
System.out.println(num1 == Integer.valueOf(128));
Integer num2 = 127;
System.out.println(num2 == Integer.valueOf(127));
System.out.println(Integer.valueOf(127) == Integer.valueOf(127));
}
실제 출력 결과는 다음과 같다.
false
true
true
별도의 참조(reference)를 할당 받았으므로 모두 false가 나와야 할 것 같은데 실제로는 false, true, true가 나온다. 이제부터 왜 이러한 결과가 나왔는지 알아보도록 하자.
2. Integer.valueOf(127) == Integer.valueOf(127)가 True인 이유, Integer 캐시
[ 원시 타입(Primitive Type)과 참조 타입(Reference Type) ]
원시 타입에는 char, int, float, double, boolean 등이 있는데, 값을 직접 들고 있는 타입이다. 그래서 다음과 같이 int 값을 비교하는 것은 실제 값을 비교하는 것이므로 항상 true가 나온다.
@Test
void compareInteger() {
System.out.println(128 == 128);
}
참조 타입에는 클래스와 인터페이스 등이 있다. 프로그래밍을 하다 보면 원시 타입을 객체로 다뤄야하는 경우가 있는데, 이때 원시 타입을 담도록 만들어진 클래스를 래퍼 클래스(Wrapper Class)라고 한다. 대표적으로 Character, Integer, Float, Double, Boolean 등이 있다.
자바에서는 컴파일러가 원시 타입과 참조 타입을 서로 호환가능하도록 도와준다. 예를 들어서 다음과 같이 int 값을 Integer이라는 Wrapper 클래스에 담을 수 있는 것은 컴파일러가 Wrapper 클래스로 자동으로 감싸주기 때문이다.
Integer num1 = 128;
// 컴파일러가 원시 타입을 참조 타입으로 바꿔줌
Integer num1 = Integer.valueOf(128);
그러므로 처음 봤던 테스트 코드는 다음과 같이 정리 가능하고, 출력 결과는 true, false로 동일하다.
@Test
void compareInteger() {
System.out.println(Integer.valueOf(128) == Integer.valueOf(128));
System.out.println(Integer.valueOf(127) == Integer.valueOf(127));
}
참조 타입은 실제 객체가 아니라 객체의 주소를 저장하고 있다. 래퍼 클래스 역시 참조 타입인 클래스의 일종이므로, 객체가 별도의 주소에 할당된다. 즉, Integer.valueOf(128)에 의해 매번 다른 객체가 만들어지므로 == 으로 비교를 하면 false가 나온다.
하지만 Integer.valueOf(127)에 의해 만들어지는 객체는 비교하면 true가 나오는데, 이는 Integer가 내부에서 캐싱을 사용해 동일한 객체를 반환하기 때문이다. 이어서 Integer 캐시에 대해 자세히 살펴보도록 하자.
[ Integer 캐시(Integer Cache) ]
Integer 클래스의 valueOf 메소드의 소스코드를 보면 다음과 같다. 단순한 코드라 어려움 없이 쉽게 읽을 수 있다.
@HotSpotIntrinsicCandidate
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
IntegerCache.low는 -128이고, high는 127이다. 즉, Integer는 내부에서 -128부터 127까지의 Integer 객체를 미리 캐싱해두고, 해당 범위의 값을 요청하면 캐싱된 값을 반환하는 것이다. 그래서 해당 범위의 값을 비교하면 같은 참조를 갖게 되므로 true가 나온다.
해당 범위의 값은 매우 자주 사용되기 때문에 메모리를 절약하고자 캐싱을 해두고 있는 것이다. 실제로 자바 Integer 클래스의 내부 스태틱 클래스로 IntegerCache라는 클래스가 선언되어 있음을 확인할 수 있다.
IntegerCache의 static block에서 값을 메모리에 초기화하고 있으므로, 캐시는 클래스가 메모리로 로딩될 때 초기화된다. 캐싱은 Integer 뿐만 아니라 Byte, Short, Long Character 클래스에도 적용된다. 범위는 조금씩 다른데, 정리하면 다음과 같다.
- Byte, Short, Long: -127부터 127까지
- Integer: -128부터 127까지
- Character: 0부터 127까지
캐시될 범위는 JVM 옵션 중 하나인 -XX:AutoBoxCacheMax로 조절할 수 있다. 하지만 이는 Integer에만 적용 가능하며 다른 클래스의 경우에는 범위를 조절할 수 없으므로 참고하도록 하자.
참고로 이렇듯 자주 사용되는 객체를 재사용하여 메모리를 절약하는 디자인 패턴을 플라이웨이트 패턴(flyweight patter)이라고 한다. 당연하게도 이때 공유되는 객체는 불변 객체여야만 한다. 플라이웨이트가 불변 객체가 아니라면 어떤 코드가 플라이웨이트 객체를 임의로 수정할 경우, 해당 객체를 공유하고 있는 다른 코드에 영향을 미치기 때문이다.
이번에는 Integer.valueOf(127) == Integer.valueOf(127)가 True인 이유인 Integer 캐시에 대해 알아보았습니다. 이 글은 Dzone의 글을 번역 및 정리한 내용입니다. 감사합니다.
'Java & Kotlin' 카테고리의 다른 글
[Java] 자바의 컨테이너 환경을 위한 XX:+UseContainerSupport 옵션 (0) | 2023.05.23 |
---|---|
[Java] JUnit의 진화 과정과 public 접근 제어자 (2) | 2023.03.07 |
[Java] CompletableFuture에 대한 이해 및 사용법 (8) | 2022.08.16 |
[Java] Callable, Future 및 Executors, Executor, ExecutorService, ScheduledExecutorService에 대한 이해 및 사용법 (12) | 2022.08.08 |
[Java] Thread와 Runnable에 대한 이해 및 사용법 (10) | 2022.08.01 |