본문 바로가기

Java

[Java] Object.clone()


요즘 머리가 터질 것 같습니다 --;

Object 클래스에 clone() 메서드가 protected로 정의 되어있습니다.
native 메서드입니다. 기본적으로는 메모리를 복사한다고 합니다.

clone()을 지원하려면 그 클래스는 Cloneable 인터페이스를 implements 해야 합니다.
하지만, 우리가 일반적으로 알고있는 것과는 다르게 Cloneable 인터페이스에는 아무런 메서드도 정의되어 있지 않습니다.

제가 처음에 clone()에 대해서 굉장히 헷갈렸던 부분입니다.

일단, clone()을 지원하지 않을 거라면, 상관없고...clone()을 지원 할 클래스라면
Object 클래스에 정의 되어있는 protected Object clone() 메서드를 public으로 재정의 해서 사용해야 합니다.

재정의하지 않으면, 기본적으로 Object의 clone() 메서드를 호출합니다만, 다른 클래스에서 이를 호출 할 수가 없습니다. protected 이기 때문입니다. (Object의 clone()메서드는 재정의하기 전까지는 protected java.lang.Object.clone() 입니다. 다른 패키지에서 호출 할 수 없습니다.)

일단 간단하게 클래스를 하나 만들어보겠습니다.

public class A implements Cloneable {
 private String name;

  public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }

public Object clone() throws CloneNotSupportedException {
  A a = (A)super.clone();
  return a;
 }

}


테스트를 해보겠습니다.

public class Test {
 public static void main(String[] args) throws CloneNotSupportedException {
  A a = new A();

  a.setName("jang");
  A b = (A) a.clone();

  System.out.println("a = " + a.getName());
System.out.println("b = " + b.getName());

  a.setName("GGG");


System.out.println("a = " + a.getName());
  System.out.println("b = " + b.getName());
 }
}

a = jang
b = jang
a = GGG
b = jang

clone()의 구현 규약에는 없는 내용입니다만, effective java에서 Joshua Bloch의 말에 따르면
"본질적으로 clone 메소드는 또 다른 종류의 생성자나 마찬가지다. 원본 객체에 피해를 주면 안되고, 복제본 객체는 클래스의 불변 규칙을 지켜야 한다."
(실제로 이게 안 지켜진다면 clone()의 의미가 있을까 싶습니다. 그냥 그 객체를 그대로 사용하고 말지..)
라고 되어있습니다. 위 테스트 클래스의 경우에는 이 것을 잘 지키고 있는 것 같습니다.

실제로 b에다가 a.clone()으로 객체를 복사한 후 a.name에 다른 값을 넣어봤더니
b는 영향이 없죠.

a == b를 해봐도 false가 나옵니다. 그럼, 단지 clone()메서드를 재정의 할 때는 super.clone()만 하면 되는 걸까요?

위의 상황이 적용되는 것은 name이 String 클래스가 불변 클래스이기 때문입니다.
(같은 이유로 기본 type일때도 마찬가지입니다.)

아래의 클래스를 보겠습니다.

public class A implements Cloneable {
 private String name;
 private ArrayList<String> list = new ArrayList<String>();

 public List getList() {
  return list;
 }
 public void setList(String value) {
  this.list.add(value);
 }

 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public Object clone() throws CloneNotSupportedException {
  A a = (A)super.clone();
  return a;
 }
}

List 클래스가 멤버로 추가되었습니다. 바로 Test를 한번 해보겠습니다.

public class Test {
 public static void main(String[] args) throws CloneNotSupportedException {
  A a = new A();
  a.setName("jang");
  a.setList("abc");
  A b = (A) a.clone();
  System.out.println("a = " + a.getList());
  System.out.println("b = " + b.getList());

  a.setList("GGG");
  a.setName("GGG");
   System.out.println("a = " + a.getList());
  System.out.println("b = " + b.getList());
  System.out.println(a == b);
 }
}

a = [abc]
b = [abc]
a = [abc, GGG]
b = [abc, GGG]
false

어라? 결과가 좀 다릅니다.

똑같은 방식으로 b에 a.clone()을 사용해서 객체 복사를 한 후 a의 멤버 클래스인 list에 값을 넣었더니 b까지 바뀌어버렸습니다.

만약에 클래스의 불변규칙을 제공하기 위해서 clone() 사용했는데 저런식의 결과가 나와버리면 디버깅 하기가 정말로 쉽지 않을 것입니다. 이런 경우에는 clone() 메서드를 재정의 해서 클래스의 불변규칙을 제공해 줘야합니다.
(저렇게 되어도 상관없다! 싶으면 그냥 쓰셔도 되겠습니다만.....;;;)

어떻게 해야 할까요?

 public Object clone() throws CloneNotSupportedException {
  A a = (A)super.clone();
  a.list = (ArrayList)list.clone();
  return a;
 }
이렇게 수정해보았습니다. ArrayList는 불변객체가 아니기 때문에 단지 A a = (A)super.clone(); 으로 각각의 멤버를 메모리복사하게 되면 위와 같은 상황에 빠질 수 있습니다. 따라서, ArrayList가 가지고 있는 clone()을 사용해서 복사를 해줘야 합니다.

실제로 ArrayList에서 재정의되고 있는 clone() 메서드는

    public Object clone() {
 try {
     ArrayList<E> v = (ArrayList<E>) super.clone();
     v.elementData = (E[])new Object[size];
     System.arraycopy(elementData, 0, v.elementData, 0, size);
     v.modCount = 0;
     return v;
 } catch (CloneNotSupportedException e) {
     // this shouldn't happen, since we are Cloneable
     throw new InternalError();
 }
    }

이런식으로 불변규칙을 제공하기 위해서 재정의 되어있습니다.

만약에, 저 멤버 클래스가 ArrayList가 아니고 사용자가 만든 클래스이고 그것이 Cloneable을 implements 하고 있다면, 그 새롭게 만들어진 클래스의 clone()메서드를 재정의 할 때는 위와 같은 상황을 제대로 고려하여 작성하여야 합니다. 그렇지않다면..디버깅이....;;;

그렇다면 마지막으로 위의 멤버 클래스가

private ArrayList<String> list = new ArrayList<String>();

가 아니라

private List<String> list = new ArrayList<String>();


로 선언이 되어있다면 어떨까요? ArrayList는 clone() 메서드를 재정의하고 있지만, List는 인터페이스이고 때문에 위 list 객체는 clone()메서드를 사용하지 못 합니다. (자바의 다형성을 참고하세요)

이럴경우에는 위의 경우처럼 List를 ArrayList로 변경하여 ArrayList가 재정의하고 있는 clone() 메서드를 사용 할 수 있도록 하던가, 아니면 A.clone()메서드에서 list의 복사를 다시 구현해 주어야 할 것 입니다.

ps. 도움주신 풍대리님 감사합니다 ^^