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

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

JV-ST-032025-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!!"를 만든 뒤 버려집니다. 반복문 안에서의 문자열 연결이 치명적인 이유가 바로 이 불필요한 임시 객체 생성 때문입니다.