'clone()'에 해당되는 글 2건

  1. 2008.12.10 Java clone()에 대한 예제 하나만 더 (2)
  2. 2008.12.05 [Java] Object.clone() (3)

http://www.codeguru.com/java/tij/tij0127.shtml 여기에 있는 예제입니다.

포스트를 작성하고나서 풍대리님이 url을 보내주셔서 한번 보았는데

예제가 참 좋습니다.

그중 하나면 여기다 올려보겠습니다.


//: Snake.java
// Tests cloning to see if destination of
// handles are also cloned.

public class Snake implements Cloneable {
  private Snake next;
  private char c;
  // Value of i == number of segments
  Snake(int i, char x) {
    c = x;
    if(--i > 0)
      next = new Snake(i, (char)(x + 1));
  }
  void increment() {
    c++;
    if(next != null)
      next.increment();
  }
  public String toString() {
    String s = ":" + c;
    if(next != null)
      s += next.toString();
    return s;
  }
  public Object clone() {
    Object o = null;
    try {
      o = super.clone();
    } catch (CloneNotSupportedException e) {}
    return o;
  }
  public static void main(String[] args) {
    Snake s = new Snake(5, 'a');
    System.out.println("s = " + s);
    Snake s2 = (Snake)s.clone();
    System.out.println("s2 = " + s2);
    s.increment();
    System.out.println(
      "after s.increment, s2 = " + s2);
  }
} ///:~
이 예제입니다.

한번 돌려보세요~ 결과가 아래와 같이 나옵니다.

s = :a:b:c:d:e
s2 = :a:b:c:d:e
after s.increment, s2 = :a:c:d:e:f

재귀호출을 이용해서 snake 클래스를 마치 linked list 처럼 만들어내고 있습니다. 5개의 연결 객체를 만들어냅니다.

Snake s = new Snake(5, 'a');


에 의해서 객체 a가 생성됩니다. 이녀석은 마치

s: c = a
   - s: c = b
       - s: c =  c
             - s: c = d 
                   - s: c = e


이런 구조로 되어있을 것입니다. Snake를 멤버로 가지고 있으니까요.

그 다음 s2에 s를 clone합니다. 이 시점에서 s2는 위 s와 동일한 구조를 가지게 됩니다.


s2: c = a
   - s: c = b
       - s: c =  c
             - s: c = d 
                   - s: c = e

여기까지는 큰 어려움은 없구요...
잘 보시면 첫번째 객체는 s2인데 이후에 달린 next들은 전부 s입니다.

일단 이상태에서 이제 s.increment()를 실행합니다.

대충 짐작은 갑니다. clone()을 한 객체가 서로 영향을 받느냐 안 받느냐..

처음에 저는 영향을 받는다면 b,c,d,e,f가 나올 것이고 영향을 안 받는다면 a,b,c,d,e가 나올 것이다..
그냥 통밥으로 그렇게 때려맞췄었던거죠..

그런데 결과는

s2 = :a:c:d:e:f 입니다. 물론 s는 s = :b:c:d:e:f 입니다.

왜 이렇게 결과가 나올까요..

일단 c가 char 즉, immutable이라는 것입니다.

Snake s2 = s.clone();

을 하게 되면 s의 복사를 하기 위해 Snake type의 껍데기를 만들고
s( c가 a를 가지고 있고 Snake:c=b인 녀석을 next로 가지고 있는)를 복사하게 되는데 c는 immutable이므로 별도의 메모리에 값이 할당됩니다.

즉, s2.c와 s.c는 전혀 별개의 값을 갖게 되는 것입니다.

하지만, next는 좀 상황이 다릅니다.

s를 s2에 복사를 하게 되면서 c는 위의 말대로 복사가 되었지만 next는 mutable 객체이기 때문에
참조값을 그대로 복사하게 됩니다. 즉 s2.next와 s.next는 같은 참조값을 가지게 되고 결국 s2.next(그리고 그 이후에 줄줄이 달린 next들)는 s.next와 동일한녀석이 되는 것입니다.

결국 clone()에 의한 복사는 첫번째 segment에서만 진행이 된 것이고 나머지는 복사라는 과정 자체가 진행되지 않았지만, 첫번째 복사가 진행되면 s2.next와 s.next가 같은 참조값을 가지게 되었기 때문에 위와 같은 결과가 나오게 되는 것입니다.


PS. 첫번째 포스팅에 오류가 있어고 풍대리님이 바로 잡아주셨습니다.



Posted by 용식

[Java] Object.clone()

Java 2008.12.05 18:59

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

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. 도움주신 풍대리님 감사합니다 ^^
Posted by 용식