클래스와 인스턴스, 그리고 복사의 기술

JV-ST-03calendar_today2025-12-18 00:00#Java #Level1

1. 클래스, 객체, 인스턴스의 정립

우리는 흔히 이 세 용어를 혼용하지만, 메모리 관점에서 명확한 구분이 필요합니다.

  • 클래스 (Class): 객체를 만들어 내기 위한 설계도, 문법, 형식입니다. .class 파일 형태로 존재하며 정적입니다.

  • 객체 (Object): 구현할 대상이자 추상적인 단위입니다.

  • 인스턴스 (Instance): 설계도(클래스)를 바탕으로 Heap 메모리에 실체화된 것입니다.

    • 참조자 (Reference): Stack 영역에 존재하며, Heap에 있는 인스턴스의 **주소(Address)**를 가리키는 리모컨입니다.

2. 생성자 (Constructor): 객체의 탄생

생성자는 객체가 new 키워드를 통해 Heap에 할당되는 순간, 가장 먼저 실행되어 초기화를 담당하는 특수 메서드입니다.

2.1. 생성자의 규칙과 특징

  1. 리턴 타입 없음void조차 적지 않습니다. 리턴 타입이 있다면 그것은 생성자가 아닌 일반 메서드입니다.

  2. 이름 일치: 반드시 클래스명과 동일해야 합니다.

  3. 초기화 우선순위: 필드 선언 시 대입한 값보다 생성자 내부의 코드가 우선합니다. (덮어쓰기)

  4. 기본 생성자: 작성하지 않으면 컴파일러가 빈 생성자(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!!"를 만든 뒤 버려집니다. 반복문 안에서의 문자열 연결이 치명적인 이유가 바로 이 불필요한 임시 객체 생성 때문입니다.