안녕하세요, 여행벌입니다.

오늘은 자바 Object 클래스의 clone 메소드에 대해서 정리해보겠습니다.


clone 메소드

 Object 클래스에는 인스턴스의 복사를 위한 clone 메소드가 정의되어 있습니다.

1
protected Object clone() throws CloneNotSupportedException
cs

 이 메소드가 호출되면, 호출된 메소드가 속한 인스턴스의 복사본이 생성되고, 이렇게 만들어진 복사본의 참조 값이 반환됩니다.

 단, Cloneable 인터페이스를 구현한 인스턴스를 대상으로만 위의 메소드를 호출할 수 있고, Cloneable 인터페이스를 구현하지 않은 클래스의 인스턴스를 대상으로 호출하면 CloneNotSupportedException 예외가 발생합니다.

 Cloneable 인터페이스는 인스턴스를 복사해도 된다는 '마커 인터페이스'로 정의해야 할 메소드가 따로 존재하지 않는 인터페이스입니다.

 

 저번 포스팅과 마찬가지로 좌표를 담는 클래스를 선언하고, clone 메소드를 이용해보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package Hello;
 
class Point implements Cloneable{
    private int xPos;
    private int yPos;
    public Point(int x, int y) {
        xPos = x;
        yPos = y;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException{
        return super.clone();
    }
    @Override
    public boolean equals(Object obj) {
        if(((Point)obj).xPos == xPos && ((Point)obj).yPos == yPos) return true;
        return false;
    }
    
}
 
public class test{
    public static void main(String args[]) {
        Point p1 = new Point(57);
        try {
            Point p2 = (Point)p1.clone();
            if(p1 == p2) System.out.println("참조 대상이 같습니다");
            else System.out.println("참조 대상이 다릅니다");
            
            if(p1.equals(p2)) System.out.println("내용이 같습니다");
            else System.out.println("내용이 다릅니다");
        }
        catch(CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}
cs

 clone 메소드를 이용하기 위해, Cloneable 인터페이스를 implements 한 것을 볼 수 있습니다.

 clone 메소드는 새로운 인스턴스를 생성하고 참조 값을 반환해주므로, "==" 연산 결과는 서로 다르지만, equals 결과는 같다는 것을 예상할 수 있습니다.

 

1
2
3
[Output]
참조 대상이 다릅니다
내용이 같습니다
cs

깊은 복사, 얕은 복사

 clone 메소드를 이용하면 대상 인스턴스의 내용을 복사해서 새로운 인스턴스를 만들 수 있습니다.

 그럼 다음과 같은 상황에서 clone 메소드는 어떻게 작용할까요?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class Point implements Cloneable{
    private int xPos;
    private int yPos;
    public Point(int x, int y) {
        xPos = x;
        yPos = y;
    }
    public void showPoint() {
        System.out.println("좌표 : " + xPos + ' ' + yPos);
    }
    public void change(int x, int y) {
        xPos = x;
        yPos = y;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException{
        return super.clone();
    }
    @Override
    public boolean equals(Object obj) {
        if(((Point)obj).xPos == xPos && ((Point)obj).yPos == yPos) return true;
        return false;
    }
    
}
 
class Line implements Cloneable{
    private Point p1;
    private Point p2;
    public Line(Point p1, Point p2) {
        this.p1 = p1;
        this.p2 = p2;
    }
    public void showPosition() {
        System.out.println("Line안에 있는 Point들의 좌표");
        System.out.println("p1의 좌표 ");
        p1.showPoint();
        System.out.println("p2의 좌표 ");
        p2.showPoint();
    }
    public void change(int x1, int y1, int x2, int y2) {
        p1.change(x1, y1);
        p2.change(x2, y2);
    }
    @Override
    protected Object clone() throws CloneNotSupportedException{
        return super.clone();
    }
    @Override
    public boolean equals(Object obj) {
        if(p1.equals(((Line)obj).p1) && p2.equals(((Line)obj).p2)) return true;
        return false;
    }
}
cs

 Point 인스턴스를 2개 가지고 있는, Line 클래스를 정의했습니다. equals 메소드는 Point 의 내용들이 모두 같으면 true, 다르면 false 를 반환하도록 Point 클래스의 equals 메소드를 이용했습니다. showPosition, change 메소드는 인스턴스 변수의 값을 출력해주고, 값을 바꿔주는 메소드입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class test{
    public static void main(String args[]) {
        Line line1 = new Line(new Point(57), new Point(34));
        Line line2;
        try {
            line2 = (Line)line1.clone();
            line1.change(1234);
            line1.showPosition();
            line2.showPosition();
        }
        catch(CloneNotSupportedException e) {}
    }
}
cs

 line1 인스턴스를 생성하고, line2는 line1 인스턴스를 clone 했습니다. 그 후, line1의 인스턴스 변수들의 값을 변경한 후, line1과 line2의 인스턴스 변수 값들을 출력해보았습니다.

1
2
3
4
5
6
7
8
9
10
11
12
[Output]
Line안에 있는 Point들의 좌표
p1의 좌표 
좌표 : 1 2
p2의 좌표 
좌표 : 3 4
 
Line안에 있는 Point들의 좌표
p1의 좌표 
좌표 : 1 2
p2의 좌표 
좌표 : 3 4
cs

 line1과 line2의 인스턴스 변수 값들이 동시에 변경된 것을 볼 수 있습니다.

 우리가 원한 clone은 line 클래스 안에 있는 Point 인스턴스 변수 p1, p2 도 새롭게 인스턴스를 생성해서 위의 그림과 같이 각각 다른 Point 인스턴스를 참조하는 "깊은 복사" 를 원했습니다.

 하지만, 결과를 보면 아래 그림과 같이 "얕은 복사"가 된 것을 확인 할 수 있습니다.

 깊은 복사를 하기 위해서는 다음과 같이 clone 메소드를 수정해야 됩니다.

	@Override
	protected Object clone() throws CloneNotSupportedException{
		Line copy = (Line)super.clone(); // Line을 먼저 copy
		copy.p1 = (Point)p1.clone();
		copy.p2 = (Point)p2.clone();
		return copy;
	}

 Line 클래스의 인스턴스 변수인 p1, p2도 각각 다시 clone 메소드를 이용해 복사한다면, 우리가 원하는 깊은 복사를 할 수 있습니다. 결과는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
[Output]
Line안에 있는 Point들의 좌표
p1의 좌표 
좌표 : 1 2
p2의 좌표 
좌표 : 3 4
 
Line안에 있는 Point들의 좌표
p1의 좌표 
좌표 : 5 7
p2의 좌표 
좌표 : 3 4
cs

 깊은 복사가 잘 이루어져서, line1과 line2가 각각 완전히 다른 인스턴스들을 참조하고 있는 것을 확인할 수 있습니다.

배열의 clone 메소드 호출

 배열도 인스턴스이므로, clone 메소드의 호출이 가능하도록 오버라이딩 되어있습니다. 하지만, 배열은 깊은 복사가 진행되도록 오버라이딩 되어 있지는 않습니다. 따라서 배열이 지니는 참조 값의 복사만 이루어질 뿐 해당 참조 값의 인스턴스까지는 복사되지 않습니다.

 즉, 배열은 얕은 복사만 가능합니다.


 

+ Recent posts