클래스와 인스턴스, 그리고 복사의 기술
글 정보
| 카테고리 | Programming/Java/Starter |
|---|---|
| 작성일 | 2025-12-18 |
| 게시 여부 | true |
| series | Java Starter |
| series-order | 3 |
| 제목 | 클래스와 인스턴스, 그리고 복사의 기술 |
1. 클래스, 객체, 인스턴스의 정립
우리는 흔히 이 세 용어를 혼용하지만, 메모리 관점에서 명확한 구분이 필요합니다.
- 클래스 (Class): 객체를 만들어 내기 위한 설계도, 문법, 형식입니다.
.class파일 형태로 존재하며 정적입니다. - 객체 (Object): 구현할 대상이자 추상적인 단위입니다.
- 인스턴스 (Instance): 설계도(클래스)를 바탕으로 Heap 메모리에 실체화된 것입니다.
- 참조자 (Reference): Stack 영역에 존재하며, Heap에 있는 인스턴스의 주소(Address)를 가리키는 리모컨입니다.
2. 생성자 (Constructor): 객체의 탄생
생성자는 객체가 new 키워드를 통해 Heap에 할당되는 순간, 가장 먼저 실행되어 초기화를 담당하는 특수 메서드입니다.
2.1. 생성자의 규칙과 특징
- 리턴 타입 없음:
void조차 적지 않습니다. 리턴 타입이 있다면 그것은 생성자가 아닌 일반 메서드입니다. - 이름 일치: 반드시 클래스명과 동일해야 합니다.
- 초기화 우선순위: 필드 선언 시 대입한 값보다 생성자 내부의 코드가 우선합니다. (덮어쓰기)
- 기본 생성자: 작성하지 않으면 컴파일러가 빈 생성자(
Default Constructor)를 자동으로 추가합니다.
2.2. this와 생성자 연결
생성자는 오버로딩(Overloading)이 가능하며, this()를 통해 다른 생성자를 호출할 수 있습니다. 단, 반드시 첫 줄에 위치해야 합니다.
Java
class Box {
int size;
// 기본값 설정을 위해 다른 생성자 호출
Box() {
this(10); // 아래의 Box(int size)를 호출
}
Box(int size) {
this.size = size;
}
}
3. 참조자(Reference)와 실체
자바에서 변수(참조자)는 인스턴스 그 자체가 아닙니다. 단순한 포인터(주소값 보관함)일 뿐입니다.
- 식별자: 이름이 있는 것은 오직 참조자뿐입니다. Heap에 있는 인스턴스는 이름 없이 주소로만 식별됩니다.
- 착각의 늪:
Member p2 = p1;은 인스턴스를 복사하는 것이 아니라, 주소값만 복사하는 것입니다. 즉, 하나의 인스턴스를 두 개의 리모컨이 가리키게 됩니다.
4. 핵심 이슈: 얕은 복사(Shallow) vs 깊은 복사(Deep)
이 파트는 실무에서 데이터 꼬임(Side Effect)을 방지하기 위해 가장 중요합니다.
4.1. 얕은 복사 (Shallow Copy)
- 방식: 객체의 참조값(주소)만 복사합니다.
- 위험성: 복사본을 수정했는데 원본이 같이 변경되는 사이드 이펙트가 발생합니다.
- 예시:
clone()메서드의 기본 동작, 단순 변수 할당.
4.2. 깊은 복사 (Deep Copy)
- 방식: 원본 인스턴스의 값(Value) 자체를 복사하여, 아예 새로운 메모리 공간(Heap)에 독립적인 인스턴스를 만듭니다.
- 해결책: 복사 생성자 (Copy Constructor)
- 자바는 C++처럼 복사 생성자를 자동으로 지원하지 않으므로, 직접 구현해야 합니다.
- RHS (Right Hand Side): 원본 객체
- LHS (Left Hand Side): 복사될 새 객체 (
this) - 참조형 필드 처리: 내부의 참조형 필드(예:
Address)까지new를 통해 재귀적으로 새로 만들어야 진정한 깊은 복사입니다.
Java
// Deep Copy의 정석: 복사 생성자 패턴
class Member {
String name;
Address addr; // 참조형 변수
// 일반 생성자
Member(String name, Address addr) {
this.name = name;
this.addr = addr;
}
// 복사 생성자 (Deep Copy)
Member(Member rhs) {
this.name = rhs.name; // String은 불변이라 참조 복사도 무관
// [핵심] 참조형은 반드시 new로 새로 생성해서 연결해야 함
// rhs.addr의 값을 가진 새로운 Address 객체를 만듦
this.addr = new Address(rhs.addr);
}
}
5. 임시 객체 (Temporary Object)와 메모리
참조 변수에 담기지 않고 즉시 사용되고 버려지는 객체를 의미합니다.
- 예시:
new Point(10, 20).print(); - 특징: 문장이 끝나는 즉시 참조가 사라지므로 GC(Garbage Collection)의 대상이 됩니다. 과도한 임시 객체 생성은 GC 부하를 일으켜 성능 저하의 원인이 됩니다.
6. String의 두 얼굴: Heap vs Constant Pool
String은 클래스이지만 리터럴("") 생성을 지원하는 특수 녀석입니다.
new String("Java"): 무조건 Heap 영역에 새로운 객체 생성. (비효율적, 주소값 다름)"Java"(리터럴): Runtime Constant Pool에 생성. 같은 문자열이 있으면 재사용. (효율적)
문자열 연결(+)의 함정
Java
String result = "Hello" + "World" + "!!";
위 코드는 실행 과정에서 "HelloWorld"라는 임시 객체를 Heap에 만들고, 최종 결과인 "HelloWorld!!"를 만든 뒤 버려집니다. 반복문 안에서의 문자열 연결이 치명적인 이유가 바로 이 불필요한 임시 객체 생성 때문입니다.