이 글에서 얻는 것
- Primitive와 Wrapper의 메모리 구조 차이를 알고, 오토박싱으로 인한 숨겨진 비용(NPE, 성능)을 피합니다.
- String Pool이 왜 존재하는지 이해하고,
==와equals()의 차이를 “주소 vs 값” 관점에서 설명할 수 있습니다. - “Java는 포인터가 없나요?“라는 질문에 Call by Value 원리로 정확하게 답할 수 있습니다.
1. int vs Integer: 단순함 뒤의 큰 차이
| 구분 | int (Primitive) | Integer (Wrapper Object) |
|---|---|---|
| 저장 위치 | Stack (값 자체) | Heap (객체 헤더 + 값) |
| Null 가능? | 불가 (0 초기화) | 가능 (null 허용) |
| 용도 | 연산, 로컬 변수 | 컬렉션(List), DTO, Null 표현 필요 시 |
| 비용 | 매우 저렴 | 객체 생성 + GC 비용 발생 |
실무 주의점: 오토박싱(Auto-boxing)과 NPE
편리함을 위해 컴파일러가 변환해주지만, 이게 독이 될 때가 있습니다.
public int calculate(Integer value) {
return value + 10; // value가 null이면 NullPointerException 터짐!
}
alue가 객체이므로 value.intValue()를 호출하는데, value가 null이면 여기서 터집니다.
“DB에서 숫자가 null일 수 있다면” 반드시 Wrapper class를 써야 하지만, 연산 전에는 null 체크가 필수입니다.
2. String Constant Pool: new String을 쓰지 마세요
String a = "hello";
String b = "hello";
String c = new String("hello");
System.out.println(a == b); // true (둘 다 Pool의 같은 주소)
System.out.println(a == c); // false (c는 힙에 새로 만든 객체)
왜 이렇게 만들었을까? (Flyweight Pattern)
포털 사이트 등에서 “HTML” 이라는 문자열은 수백만 번 쓰입니다. 이걸 매번 새 메모리에 할당하면 낭비겠죠?
그래서 String Pool이라는 특별한 공간(Heap 내부)에 하나만 두고 공유해서 씁니다.
new String("...")은 이 메커니즘을 무시하고 강제로 힙에 만드므로, 메모리 낭비의 주범입니다. (절대 쓰지 마세요)
3. Java는 Call by Value일까 Reference일까?
면접 단골 질문이자, 주니어들이 가장 많이 헷갈리는 부분입니다. 정답: Java는 언제나 Call by Value 입니다.
“객체를 넘기면 변경되던데요?”
void modify(Person p) {
p.setName("New Name"); // 변경됨
}
void swap(Person p1, Person p2) {
Person temp = p1;
p1 = p2;
p2 = temp; // 밖에서는 안 바뀜!
}
- 객체를 넘길 때, “객체의 주소값(Reference)“을 복사해서 전달하기 때문입니다.
- 복사된 주소(리모컨)를 통해
setName을 하면 원본 집의 가구는 바꿀 수 있습니다. - 하지만 리모컨 자체(
p1)를 다른 리모컨으로 바꿔도(swap), 원본 함수가 가진 리모컨은 그대로입니다.
실무 요약
- 루프 안에서
String + String하지 마세요 (StringBuilder 쓰세요). Integer비교는 반드시equals()로 하세요 (-128~127 캐싱 범위 밖은==이 false입니다).- “이 함수 안에서 객체를 new로 바꿔치기해도” 밖에는 영향이 없음을 기억하세요.
💬 댓글