강의

멘토링

커뮤니티

인프런 커뮤니티 질문&답변

sjn0125님의 프로필 이미지
sjn0125

작성한 질문수

기초 탄탄! 독하게 시작하는 Java - Part 2 : OOP와 JVM

[실습] MyString에 복사 생성자 추가

[실습] MyString에 복사 생성자 추가

작성

·

12

·

수정됨

0

import java.lang.reflect.Field;

class MyString {
    private byte[] sdata = null;

    MyString(MyString s) {
        this.sdata = s.sdata;
    }

    // 생성자 1: 문자열을 받을 때
    MyString(String param) {
        this.setString(param);
    }

    // 생성자 2: 정수를 받을 때
    MyString(int param) {
        setString(String.format("%d", param));
    }

    // 데이터를 문자열로 반환 (null 체크 포함)
    public String getString() {
        if (sdata == null || sdata.length <= 0) {
            return "(null)";
        }
        return new String(sdata);
    }

    // 데이터를 안전하게 복사해서 저장 (Deep Copy)
    public void setString(String param) {
        sdata = param.getBytes();
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        MyString src = new MyString("Hello");
        MyString dst = new MyString(src);

        Field field = MyString.class.getDeclaredField("sdata");
        field.setAccessible(true);

        byte[] srcSdata = (byte[]) field.get(src);
        byte[] dstSdata = (byte[]) field.get(dst);

        System.out.println("src.sdata 주소: " + System.identityHashCode(srcSdata));
        System.out.println("dst.sdata 주소: " + System.identityHashCode(dstSdata));
        // 둘이 같은 숫자 → 같은 배열 가리킴 (얕은 복사 증명!)

        src.setString("world");

        byte[] srcSdata2 = (byte[]) field.get(src);
        byte[] dstSdata2 = (byte[]) field.get(dst);

        System.out.println("--- setString 후 ---");
        System.out.println("src.sdata 주소: " + System.identityHashCode(srcSdata2));
        System.out.println("dst.sdata 주소: " + System.identityHashCode(dstSdata2));
        // src는 새 주소, dst는 그대로 → 분리됨 확인!
    }
}
  • 원래 의도가 얕은 복사를 먼저 해봄으로서 사이드 이팩트 문제를 일으킨 다음, 깊은 복사를 시도해볼려고 했는데 위 코드를 실행해보시면 깊은 복사가 되는 것 처럼보이더군요.

  • 그런데 알고보니, 의도대로 dst.sdata 배열의 주소값이 src.sdata 배열의 주소값으로 얕은 복사가 이뤄졌으며, getBytes() 함수 자체가 원래 String 객체의 데이터를 byte 배열로 새로 만들어서 해당 배열에 대한 주소값을 반환하는 함수다 보니, src.sdata 변수의 대상 인스턴스 주소 값이 새로운 바이트 배열에대한 주소값으로 바뀌어 버렸고, 기존 배열은 원래 gc에 의해 사라져야하는게 맞는데, dst.sdata에 src.sdata 배열의 주소값이 얕은 복사가 이뤄졌다보니, 기존 참조된 배열은 계속 참조되어 메모리 상에 남아있더라구요.

  • 그렇다보니, src.sdata가 참조한 실질 배열 데이터는 dst.sdata에 얕은 복사해버리고, src.sdata 배열 본인은 새로운 배열 데이터로 갈아타다보니, 실질적으로는 src 객체의 문자열을 바뀌었다기 보단, 참조한 대상 자체가 바뀌다 보닌까 실질적으로는 처음 의도한 얕은 복사로 인한 사이트 이팩트 문제는 애초부터 발생하지 않는다 라는게 결론입니다.

  • 따라서 주어진 실습 예제가 적절한지에 대해 한 번 고민해보았습니다.

  • 혹시라도 제가 부족하거나 다르게 이해한 부분이 있을 시 지적해주시면 감사하겠습니다.

 

답변 1

0

안녕하세요, 인프런 AI 인턴입니다. sjn0125님!

질문해주신 내용을 바탕으로 Java에서의 얕은 복사와 깊은 복사 과정에서 기본적으로 발생할 수 있는 상황들을 정리해보겠습니다.

얕은 복사와 깊은 복사

  • 얕은 복사(Shallow Copy): 객체의 참조 주소만을 복사합니다. 그래서 복사된 두 객체가 같은 참조를 공유하고, 하나가 변하면 다른 하나도 영향을 받습니다.
  • 깊은 복사(Deep Copy): 객체를 완전히 복제합니다. 즉, 원본과 복제된 객체가 서로 독립적인 메모리를 가집니다.

코드 분석

  1. MyString 생성자:

    • MyString(MyString s): 현재 얕은 복사가 일어나는 코드로 보입니다. s.sdata의 참조를 복사하므로 두 객체가 같은 배열을 공유하게 됩니다.
  2. setString 메서드:

    • 이 메서드에서 getBytes()를 통해 새로운 바이트 배열을 생성하여 sdata에 할당합니다.
    • 이로 인해 srcsdata가 새로운 배열을 가리키게 되고, 얕은 복사를 통해 dstsrc가 같은 배열을 가리키는 상황은 사라지게 됩니다.
  3. 메모리 주소 확인:

    • 코드를 실행한 결과, srcdstsdata가 초기에는 같은 배열을 가리키게 되지만, setString을 호출한 후에는 다른 배열을 가리키게 됩니다.

결론과 추가 설명

  • setString에 의해 원래 의도했던 얕은 복사 상황이 무효화됩니다. setString이 새로운 배열을 생성하여 할당하기 때문에 srcdstsdata가 나중에 분리되는 것입니다.
  • 이러한 점을 방지하려면, MyString 생성자에서 깊은 복사를 수행하여 s.sdata의 복제본을 this.sdata에 할당해야 합니다. 이렇게 하면, setString을 호출하기 전부터 두 객체의 sdata가 독립적으로 존재할 수 있습니다.

유사한 질문에 대한 해결 방법이 포함된 참고 링크를 통해 깊이 있는 정보를 얻으실 수 있습니다:

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

sjn0125님의 프로필 이미지
sjn0125

작성한 질문수

질문하기