들어가며
Java를 배우다 보면 "참조 타입"이라는 말을 자주 접하게 됩니다.
변수에 값을 직접 저장하는 기본 타입(int, boolean 등)과 달리, 참조 타입은 조금 다르게 동작합니다.
이 글에서는 참조 타입이 무엇인지, 메모리 구조와 함께 쉽게 설명해드리겠습니다.
기본 타입 vs 참조 타입
Java의 변수는 크게 두 가지 방식으로 값을 저장합니다.
| 항목 | 기본 타입 (Primitive) | 참조 타입 (Reference) |
|---|---|---|
| 종류 | int, long, double, boolean 등 | 클래스, 배열, 인터페이스, String 등 |
| 저장 위치 | 스택(Stack) 메모리 | 스택에는 주소, 실제 데이터는 힙(Heap) |
| 저장 내용 | 값 자체 | 힙 메모리의 주소 |
| 기본값 | 0, false 등 | null |
참조 타입의 메모리 구조
참조 타입은 변수에 객체 자체를 저장하지 않고, 객체가 저장된 힙 메모리의 주소를 저장합니다.
그 주소를 통해 실제 객체에 접근하는 방식입니다.
[스택 메모리] [힙 메모리]
┌──────────┐ ┌──────────────────┐
│ member │──주소───▶│ Member 객체 │
│ 0x1234 │ │ name = "홍길동" │
└──────────┘ │ age = 25 │
└──────────────────┘
변수 member에는 객체의 실제 데이터가 아닌, 힙 메모리의 주소(예: 0x1234)가 저장됩니다.
Java는 이 주소를 따라가서 실제 객체에 접근합니다.
코드로 이해하기
public class Main {
public static void main(String[] args) {
// 기본 타입: 스택에 값 직접 저장
int a = 10;
// 참조 타입: 힙에 객체 생성, 스택에는 주소 저장
Member member = new Member("홍길동", 25);
System.out.println(member.name); // 주소를 통해 힙의 객체에 접근
}
}
new Member()를 호출하면 힙 메모리에 객체가 생성되고,
변수 member에는 그 객체의 주소값이 저장됩니다.
참조 타입의 특징
1. null 할당 가능
참조 타입은 아무 객체도 가리키지 않는 상태를 나타내는 null을 저장할 수 있습니다.
Member member = null; // 아무 객체도 참조하지 않음
null 상태의 변수를 사용하면 NullPointerException이 발생하므로 주의해야 합니다.
Member member = null;
System.out.println(member.name); // NullPointerException 발생!
2. 같은 객체를 여러 변수가 참조 가능
Member a = new Member("홍길동", 25);
Member b = a; // a와 b가 같은 객체를 가리킴
b.name = "김철수";
System.out.println(a.name); // "김철수" 출력 - a도 바뀜!
b = a는 객체를 복사하는 게 아니라 주소를 복사하는 것입니다.
그래서 b를 통해 수정하면 a도 영향을 받습니다.
3. == 비교는 주소를 비교
Member a = new Member("홍길동", 25);
Member b = new Member("홍길동", 25);
System.out.println(a == b); // false - 주소가 다름
System.out.println(a.equals(b)); // 내용 비교는 equals() 사용
참조 타입에서 ==는 내용이 같은지가 아니라, 같은 객체를 가리키는지 비교합니다.
내용을 비교하려면 equals()를 사용해야 합니다.
String도 참조 타입
String은 기본 타입처럼 보이지만 사실 참조 타입입니다.
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
System.out.println(s1 == s2); // true - 같은 상수 풀 주소
System.out.println(s1 == s3); // false - new로 생성하면 힙에 새 객체
System.out.println(s1.equals(s3)); // true - 내용은 같음
"hello"처럼 리터럴로 선언하면 String 상수 풀에 저장되어 같은 주소를 공유합니다.
반면 new String()으로 생성하면 힙에 새 객체가 만들어져 주소가 달라집니다.
그래서 String 비교는 항상 equals()를 사용하는 것이 안전합니다.
정리
참조 타입은 객체가 저장된 힙 메모리의 주소를 변수에 저장하고, 그 주소를 통해 실제 객체에 접근하는 방식입니다.
핵심 포인트는 아래와 같습니다.
- 변수에는 객체 자체가 아닌 힙 메모리의 주소가 저장된다.
- 참조 타입 변수끼리 대입하면 주소가 복사되어 같은 객체를 가리킨다.
null은 아무 객체도 참조하지 않는 상태이며, 사용 시NullPointerException이 발생한다.- 참조 타입의 내용 비교는
==가 아닌equals()를 사용한다.
메모리 구조를 이해하면 객체 복사, NullPointerException, 예상치 못한 값 변경 등 자주 겪는 버그의 원인을 훨씬 빠르게 파악할 수 있습니다.
'-- 오늘 있었던 개발 일기' 카테고리의 다른 글
| 추상 메서드(Abstract Method)란? (0) | 2026.03.31 |
|---|---|
| 객체지향 프로그래밍(OOP)이란? (0) | 2026.03.19 |
| 브라우저 렌더링 원리 (0) | 2026.02.20 |
| 동기(Sync)와 비동기(Async) (0) | 2026.02.20 |
| Redis 특징 완벽 정리! (0) | 2026.02.20 |