객체를 하나 복제하는 것을 말한다. 구현 범위에 따라 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
아래는 복제하고자 하는 객체의 멤버들이 모두 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
결과는 아래와 같이 나온다. 앞에 설명했듯 원본과 복제는 같은 타입이지만 다른 인스턴스이다.
이번엔 아래의 예시를 보자.
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: 「이것이 자바다」, 신용권