[Java] Object.clone()

Hyeonsu Bang·2021년 11월 29일
0

Java Basic

목록 보기
6/11
post-thumbnail

Object.clone()

객체를 하나 복제하는 것을 말한다. 구현 범위에 따라 thin clone, deep clone가 있다. 복제하고자 하는 클래스는 Cloneable 인터페이스를 구현하여 해당 클래스가 복제 가능하다고 명시해주어야 한다. 그리고 clone() 을 호출할 때에는 CloneNotSupportedException을 예외처리 해주어야 한다.



clone()은 복제한 클래스의 필드값을 복사한다. 정확히는 같은 클래스 타입의 인스턴스를 만들고 필드 내의 필드를 원본 클래스의 값들로 초기화하는 것이다 (primitive의 경우는 값 자체를 복사하고, reference 타입은 reference 번지를 복사해서 대입한다). 따라서 기본적으로는 복제, 원본 간에 동등성이 있으나 동일성은 없다.


x.clone() != x; // true
x.clone().equals(x); // true
x.clone().getClass() == x.getClass(); // true


뒤에 설명하겠지만, 원본과 복제가 완벽하게 독립적으로 존재하게 하려면 `clone()`을 오버라이딩 해야할 수 있다. 만약 원본의 필드값이 `primitive`이거나 immutable 상태이면 복제 객체에서 필드를 변경해도 원본과는 상관없지만, mutable한 reference 타입을 변경하면 복제에서 변경된 내용이 원본에도 적용되기 때문이다. 전자와 후자의 경우를 고려했을 때의 차이를 thin clone, deep clone이라고 한다.



thin clone

아래는 복제하고자 하는 객체의 멤버들이 모두 primitive인 경우이다. 이런 경우에는 단순한 복사, 얕은 복사를 해도 원본과 복제가 독립적으로 존재한다고 봐도 무방하다.



public class Author implements Cloneable{

    private String id;
    private String name;
    private String age;
    private String country;

    public Author(String id, String name, String age, String country) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.country = country;
    }

    public Author() {
    }

//getter/setter / toString()

public Author cloneAuthor(){
        Author cloned = null;
        try {
            cloned = (Author) this.clone();
        } catch (CloneNotSupportedException e){
            e.printStackTrace();
        }
        return cloned;
    }

위와 같이 clone() 오버라이딩 할 때 위임하는 예외를 잡아주는 코드를 써주면 된다. 그리고 Cloneable 인터페이스를 구현해야 한다.



public class CloneTest {

    public static void main(String[] args) {

        Author author1 = new Author("1","franz kafka","30","czechia");

        Author clonedAuthor = author1.cloneAuthor();
        clonedAuthor.setName("Guy de Maupassant");

        System.out.println(author1.toString());
        System.out.println(clonedAuthor.toString());

				System.out.println(author1.equals(clonedAuthor));
        System.out.println(author1.getClass() == clonedAuthor.getClass());
        System.out.println(author1 == clonedAuthor);

    }
}
Author{id='1', name='franz kafka', age='30', country='czechia'}
Author{id='1', name='Guy de Maupassant', age='30', country='czechia'}

false
true
false

결과는 아래와 같이 나온다. 앞에 설명했듯 원본과 복제는 같은 타입이지만 다른 인스턴스이다.



deep clone

이번엔 아래의 예시를 보자.



public class CloneTest {

    public static void main(String[] args) {

        Author author1 = new Author("1","franz kafka","30","czechia");
        String[] books = {"the Metamorphosis", "a country doctor"};
        author1.setBooks(books);

        Author clonedAuthor = author1.cloneAuthor();

        clonedAuthor.setName("Guy de Maupassant");
        clonedAuthor.getBooks()[0] ="bel ami";

    }
}


original :Author{id='1', name='franz kafka', age='30', country='czechia', books=[the Metamorphosis, a country doctor]}
cloned   :Author{id='1', name='franz kafka', age='30', country='czechia', books=[the Metamorphosis, a country doctor]}

==========before mod============

modification from the cloned

original :Author{id='1', name='franz kafka', age='30', country='czechia', books=[**bel ami**, a country doctor]}
cloned   :Author{id='1', name='Guy de Maupassant', age='30', country='czechia', books=[**bel ami**, a country doctor]}


앞선 예시와 기본 틀은 같지만, book이라는 String[]이 필드로 추가되었다. 배열은 자바에서 reference type에 해당한다. 결과를 보면 primitive인 필드가 수정되었을 경우에 원본과 복제에 독립적으로 값이 수정되지만, 복제 객체에서 참조하고 있는 객체의 값을 bel ami 로 수정했더니 원본도 값이 변하는 걸 확인할 수 있다.



이것은 원본과 복제 모두 같은 배열의 참조 값을 가지고 있기 때문이다. 현재 참조된 객체는 mutable하므로 한 쪽에서 값을 수정하면 참조하고 있는 다른 한 쪽에도 영향을 미치게 된다. 즉, 원본과 복제 객체의 완벽한 독립이 이루어지지 않는다.



이것을 해결하려면 clone()을 오버라이딩하여, 복제 시에 reference 타입의 멤버들도 새로 만들어 해당 참조 값을 대입해주어야 한다. 아래의 코드는 Author 클래스에 Object.clone()을 오버라이딩 한 것이다.



	@Override
    	protected Object clone() throws CloneNotSupportedException {
        // thin clone
          Author cloned = (Author) super.clone();
          // deep clone
          cloned.books = Arrays.copyOf(this.books, this.books.length);
          cloned.biography = new Biography();
          return cloned;
   	}


primitive 또는 immutable한 필드는 Object.clone()을 통해 복제를 한다. 그리고 mutable한 reference type의 필드인 books 배열과 Biography의 경우 새로 참조를 만들어 복제 객체의 해당 필드에 대입해준다. 이제 타입은 같지만 다른 인스턴스인 객체를 참조하게 된다.



마지막으로 수정된 코드는 아래와 같다.

public class CloneTest {

    public static void main(String[] args) {
				// author1 
        Author author1 = new Author("1","franz kafka","41","czechia");
        String[] books = {"the Metamorphosis", "a country doctor"};
        author1.setBooks(books);

        Biography bio1 = new Biography("1883","1924");
        author1.setBiography(bio1);

				// clone 
        Author clonedAuthor = author1.cloneAuthor();

				// modification in clone
        clonedAuthor.setName("Guy de Maupassant");
        clonedAuthor.setAge("43");
        clonedAuthor.getBooks()[0] ="bel ami";
        clonedAuthor.getBiography().setBirth("1850");
        clonedAuthor.getBiography().setDeath("1893");

    }
}

출력하면 아래와 같다.

original :Author{id='1', name='franz kafka', age='41', country='czechia', books=[the Metamorphosis, a country doctor], biography=Biography{death='1924', birth='1883'}}
cloned   :Author{id='1', name='franz kafka', age='41', country='czechia', books=[the Metamorphosis, a country doctor], biography=Biography{death='null', birth='null'}}

==========before mod============

modification from the cloned

original :Author{id='1', name='franz kafka', age='41', country='czechia', books=[the Metamorphosis, a country doctor], biography=Biography{death='1924', birth='1883'}}
cloned   :Author{id='1', **name='Guy de Maupassant**', age='43', country='czechia', books=[**bel ami, a country doctor**], biography=Biography{**death='1893', birth='1850'}**}

위와 같이 복제된 객체에서의 변경은 복제 객체 내에서만 일어난 것을 확인할 수 있다.




source: github

reference: 「이것이 자바다」, 신용권

profile
chop chop. mish mash. 재밌게 개발하고 있습니다.

0개의 댓글