이번 글은 Java 면접에서 가장 유명한 함정 문제 중 하나다.
면접관은 종종 다음 코드를 보여준다.
Integer a = 100;
Integer b = 100;
System.out.println(a == b);
그리고 묻는다.
결과는 무엇일까요?
많은 사람들이
Wrapper 객체니까
new Integer()
↓
객체 두 개 생성
↓
false
라고 대답한다.
하지만 실제 결과는
true
이다.
왜 그럴까?
Integer Cache
자주 사용하는 Integer 객체를 미리 생성해 재사용하는 JVM 최적화 기능
먼저 Integer는 객체이다.
Integer number = 100;
우리는 이렇게 작성한다.
AutoBoxing 글에서 공부했듯이
컴파일러는
Integer number =
Integer.valueOf(100);
로 변경한다.
여기서 중요한 것은
new Integer()가 아니라 Integer.valueOf()를 사용한다는 점이다.
Integer.valueOf() 내부
실제로 JDK 내부는 매우 단순하다.
(이해하기 쉽게 단순화한 코드)
public static Integer valueOf(int value) {
if (value >= -128 && value <= 127) {
return IntegerCache.cache[value + 128];
}
return new Integer(value);
}
즉,
-128 ~ 127
↓
미리 만들어 둔 객체 반환
-----------------------
그 외 숫자
↓
새 객체 생성
이다.
실제 동작
Integer a = 100;
Integer b = 100;
컴파일러는
Integer a = Integer.valueOf(100);
Integer b = Integer.valueOf(100);
로 변경한다.
그리고
Integer.valueOf(100)
↓
Cache 조회
↓
같은 객체 반환
이다.
메모리
Integer Cache
-------------------
100 객체
-------------------
▲
│
│
a b
둘 다 같은 객체를 참조한다.
그래서
System.out.println(a == b);
결과는
true
이다.
이번에는 1000
Integer a = 1000;
Integer b = 1000;
컴파일러
Integer a = Integer.valueOf(1000);
Integer b = Integer.valueOf(1000);
하지만
1000
↓
Cache 없음
↓
new Integer()
↓
new Integer()
이다.
메모리
Heap
Integer(1000)
▲
│
a
Integer(1000)
▲
│
b
서로 다른 객체이다.
그래서
a == b
결과는
false
이다.
왜 -128 ~ 127일까?
JVM 개발자들은
통계를 보면
0
1
-1
10
100
같은 작은 숫자가 압도적으로 많이 사용된다는 것을 알았다.
그래서
애플리케이션 시작
↓
Integer Cache 생성
↓
-128 ~ 127 객체 미리 생성
↓
계속 재사용
하도록 만들었다.
그림으로 이해하기
애플리케이션 시작
Integer Cache
----------------------------------
-128
-127
...
0
1
2
...
127
----------------------------------
이미 생성되어 있다.
사용
Integer.valueOf(10);
↓
Cache
↓
10 객체 반환
새 객체를 만들지 않는다.
new Integer()는 다르다.
Integer a = new Integer(100);
Integer b = new Integer(100);
항상
new
↓
Heap
↓
새 객체 생성
이다.
메모리
Heap
Integer(100)
▲
│
a
Integer(100)
▲
│
b
그래서
a == b
는
false
이다.
equals()는 항상 값 비교
Integer a = 1000;
Integer b = 1000;
System.out.println(a.equals(b));
결과
true
이다.
왜냐하면
Integer의 equals는
public boolean equals(Object obj) {
return this.value == other.value;
}
처럼
값(value)을 비교하기 때문이다.
== 와 equals()
이 차이를 반드시 이해해야 한다.
==
참조 비교
같은 객체인가?
메모리
a ─────────┐
▼
Integer(100)
▲
b ─────────┘
↓
true
a ───► Integer(1000)
b ───► Integer(1000)
↓
false
equals()
값 비교
100 == 100
↓
true
1000 == 1000
↓
true
Long도 Cache가 있다.
Long a = 100L;
Long b = 100L;
결과
true
Character도 있다.
Character a = 'A';
Character b = 'A';
결과
true
Boolean은 더 단순하다.
Boolean.TRUE
Boolean.FALSE
딱 두 개만 존재한다.
실무에서는 ==를 사용할까?
거의 사용하지 않는다.
잘못된 코드
Integer a = getValue();
if (a == 100) {
}
이 코드는
AutoUnboxing 때문에
if (a.intValue() == 100)
가 되어 의도와 다르게 동작하거나, a가 null이면 NullPointerException이 발생할 수 있다.
또 다른 예
Integer a = 1000;
Integer b = 1000;
if (a == b) {
}
결과
false
이다.
그래서 실무에서는
Objects.equals(a, b)
또는
a.equals(b)
를 사용한다.
JVM 옵션으로 Cache 범위 변경
흥미롭게도 Cache 범위는 확장할 수 있다.
-XX:AutoBoxCacheMax=1000
처럼 설정하면
-128 ~ 1000
까지 Cache를 사용할 수 있다.
하지만 일반적인 애플리케이션에서는 거의 사용하지 않는다.
AutoBoxing과 연결해서 다시 보면
Integer a = 100;
실제로는
AutoBoxing
↓
Integer.valueOf(100)
↓
Integer Cache 조회
↓
기존 객체 반환
이다.
그림으로 전체 정리
Integer a = 100;
↓
AutoBoxing
↓
Integer.valueOf(100)
↓
Integer Cache
↓
기존 객체 반환
↓
a
Integer a = 1000;
↓
AutoBoxing
↓
Integer.valueOf(1000)
↓
Cache 없음
↓
new Integer(1000)
↓
a
면접 포인트
Integer Cache란?
자주 사용하는 Integer 객체(-128 ~ 127)를 미리 생성하여 재사용하는 JVM 최적화 기능이다.
왜 존재하는가?
작은 숫자는 매우 자주 사용되므로 객체 생성을 줄이고 메모리 사용량과 GC 부담을 줄이기 위해서이다.
== 와 equals() 차이는?
- == : 객체의 참조(주소) 비교
- equals() : 객체의 값 비교
실무에서는?
Wrapper 객체(Integer, Long 등)는 ==보다 equals() 또는 Objects.equals()를 사용하는 것이 안전하다.
다음 글 추천
이 흐름에서는 StringBuilder vs StringBuffer가 가장 자연스럽다.
단순히
"StringBuilder가 빠르고 StringBuffer는 synchronized"
수준이 아니라,
- 왜 String은 immutable인가?
- + 연산은 실제로 어떻게 동작하는가?
- 컴파일러가 언제 StringBuilder로 변환하는가?
- 왜 반복문에서는 +를 피해야 하는가?
까지 연결해서 이해할 수 있다.
'language > java' 카테고리의 다른 글
| BigDecimal을 사용하는 이유 (0) | 2026.06.16 |
|---|---|
| StringBuilder vs StringBuffer (0) | 2026.06.16 |
| AutoBoxing / Unboxing (0) | 2026.06.16 |
| Serialization / Deserialization (0) | 2026.06.16 |
| JDK Proxy vs CGLIB (0) | 2026.06.16 |
댓글