JAVA/BASIC

자바 Object 클래스 객체복제 (얇은복제)

유혁스쿨 2023. 4. 10. 21:00
728x90
반응형

객체 복제란 원본 객체의 필드값과 동일한 값을 가지는 새로운 객체를 생성하는 것을 말합니다.

객체를 복제하는 이유는 원본 객체를 안전하게 보호하기 위해서 입니다.

신뢰하지 않는 영역으로 원본 객체를 넘겨 작업할 경우 원본 객체의 데이터가 훼손될 수 있습니다.

따라서 복제된 객체를 만들어 신뢰하지 않는 영역으로 넘기는 것이 좋습니다.
복제된 객체의 데이터가 훼손되더라도 원본 객체는 아무런 영향을 받지 않기 때문에 안전하게 데이터를 보호할 수 있게 됩니다.

객체를 복제하는 방법에는 얕은 복제와 깊은 복제가 있으며 얕은 복제에 대해서 가볍게 알아보도록 합니다.

 

 

얕은복제(thin clone)

단순히 필드 값을 복사해서 객체를 복제하는 것을 말합니다.

필드값만 복제하기 때문에 필드가 기본 타입일 경우 값 복사가 일어나고, 필드가 참조 타입일 경우에는 객체의 번지가 복사됩니다.

예를들어 원본 객체에 int타입의 필드와 int타입의 배열 필드가 있을 경우 얕은 복제된 객체의 필드값은 다음과 같습니다.

원본, 복제 객체 각각 primitive int타입 필드의 경우 변수는 스택영역에 각각 따로 저장되며, 저장될 정수 데이터는 메소드 영역에 각각 따로 저장됩니다.

하지만 reference Array타입 필드의 경우 변수는 스택영역에 각각 따로 저장되나, 저장될 객체는 힙영역에 단 하나만 생성되며 원본, 복제 객체 모두 동일한 하나의 배열 객체 주소를 가르키게 됩니다.

 

 

Object - clone()

Object의 clone() 메소드는 자신과 동일한 필드값을 가진 얕은 복제된 객체를 리턴합니다.

해당 메소드로 객체를 복제하려면 원본 객체는 반드시 java.lang의 Cloneable인터페이스를 구현하고 있어야 합니다.

메소드 선언이 없음에도 불구하고 Cloneable 인터페이스를 명시적으로 구현하는 이유는 클래스 설계자가 복제를 허용한다는 의도적인 표시를 하기 위해서 입니다.

클래스 설계자가 복제를 허용하지 않으면 clone() 메소드를  호출할 때 CloneNotSupportException 예외가 발생하여 복제가 실패됩니다.

따라서 Clone()을 호출할 경우 try-catch 구문을 사용하여 예외처리를 해줍니다.

아래 예제를 통해 얕은 복제를 해보겠습니다.

[Member.java]

class Member implements Cloneable {
    public String id;
    public String name;
    public String password;
    public int age;
    public int[] ageArray;
    public boolean adult;


    public Member(String id, String name, String password, int age, int[] ageArray, boolean adult) {
        this.id = id;
        this.name = name;
        this.password = password;
        this.age = age;
        this.ageArray = ageArray;
        this.adult = adult;
    }

    public Member getMember() {
        Member cloned = null;
        try {
            cloned = (Member) clone();
        } catch (CloneNotSupportedException e) {
        }
        return cloned;
    }

    @Override
    public String toString() {
        return "Member{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                ", age=" + age +
                ", ageArray=" + Arrays.toString(ageArray) +
                ", adult=" + adult +
                '}';
    }
}
public class Example {
    public static void main(String[] args) {
        Member original = new Member("blue", "홍길동", "12345", 25, new int[] {26, 0, 0}, true);

        Member cloned = original.getMember();
        cloned.password = "67890";
        System.out.println(cloned);
        System.out.println(original);
    }
}

cloned객체는 12345에서 67890으로 변경되었으나, original객체의 password 값은 변함없이 12345를 출력합니다.

 

 

얕은 복제에서 참조타입 필드

얕은 복제의 경우 참조 타입 필드는 번지 즉, 주소값만 복제되기 때문에 원본 객체의 필드와 복제 객체의 필드는 같은 객체를 참조하게 됩니다.

만약 복제 객체에서 참조 객체를 변경하면 원본 객체도 변경된 객체를 가지게 됩니다.

이것이 얕은 복제의 단점 입니다.

아래의 얕은 복제 예제를 통해 참조타입 필드를 변경하였을 때 어떤 결과를 가져오는지 확인합니다.

public class Example {
    public static void main(String[] args) {
        Member original = new Member("blue", "홍길동", "12345", 25, new int[] {26, 0, 0}, true);

        Member cloned = original.getMember();
        cloned.password = "67890";
        System.out.println(cloned.ageArray); // [I@3a82f6ef
        System.out.println(original.ageArray); // [I@3a82f6ef
        cloned.ageArray[1] = 3; // 복제 객체의 참조타입 필드 값 변경
        System.out.println(cloned.ageArray); // [I@3a82f6ef
        System.out.println(original); // Member{,,,,ageArray=[26,3,0],} - 26,0,0 에서 26,3,0으로 변경됨
        System.out.println(original.ageArray); // [I@3a82f6ef


    }
}

복제 객체와 원본 객체의 참조 타입 필드가 모두 같은 주소값을 가지는 것을 확인할 수 있으며,

복제 객체의 참조 타입 필드인 ageArray 배열의 인덱스 1번의 값을 3으로 변경하였더니 원본 객체의 ageArray 배열의 인덱스 1번의 값도 0에서 3으로 변경된 것을 확인 할 수 있습니다.

 

다음 포스팅 에서는 Object의  깊은 복제(Deep Clone)에 대해서 다루도록 하겠습니다. 감사합니다.

 

 

728x90
반응형