java clone 메서드

박시시·2022년 9월 17일
0

JAVA

목록 보기
1/13

대입 연산자를 통한 복제

개발을 하다 보면 객체를 복제해서 사용하고 싶을 때가 있다. 단순히 생각한다면 아래와 같이 대입연산자를 사용해 복제를 하려 할 것이다

class Test {
    int x, y;
    
    Test() {
        x = 10;
        y = 20;
    }
}

public class TestCloning(){
	public static void main(String[] args) {
		Test obj1 = new Test();
		Test obj2 = obj1;
    }
}

만약 위의 코드에서 obj2의 멤버변수의 값을 변경하면 어떻게 될까.

public class TestCloning(){
	public static void main(String[] args) {
		Test obj1 = new Test();
		Test obj2 = obj1;
        
        obj2.x = 100;
        
        System.out.println(ob1.x);
        System.out.println(ob2.x);        
    }
}

결과

100
100

obj2의 필드값을 변경했음에도 원본 객체의 값까지 변경된 걸 볼 수 있다. 대입연산자를 통해 복제를 하고자 할 경우 객체가 아닌 원본 객체의 메모리주소값이 대입된다. 즉, 두 변수 obj1, obj2가 메모리 상에서 같은 곳을 바라보고 있다는 뜻이다.

clone() 메서드를 통한 복제

Object의 clone() 메서드는 현재 객체의 클래스의 새 인스턴스를 만들고 원본 객체의 필드 내용을 사용하여 새로이 생성된 객체의 모든 필드를 초기화한다.
이 clone() 메서드를 사용하게 된다면 좀 더 우리가 원하는 복제와 가까운 결과를 얻어 낼 수 있을 것이다.

위의 코드를 조금 수정해 보자.

class Test implements Cloneable {
    int x, y;
    
    public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
}

public class TestCloning(){
	public static void main(String[] args) {
		Test obj1 = new Test();
        obj1.x = 10;
        obj1.y = 20;
        
		Test obj2 = (Test)obj1.clone();
        
        obj2.x = 100;
        
        System.out.println(ob1.x);
        System.out.println(ob2.x);        
    }
}

결과

10
100

이와 같이 clone() 메서드를 사용한다면 복제된 객체에서 필드값을 변경해도 원본 객체의 필드에는 영향을 미치지 않는다.

Cloneable Interface

위 코드를 보게 되면 Cloneable 인터페이스를 구현한 것을 볼 수 있다.
만약 해당 인터페이스를 구현하지 않고 clone 메서드를 사용하게 되면 CloneNotSupportedException 에러가 발생하게 된다.
복제를 하고자 하는 클래스에 해당 인터페이스를 구현함으로써 이 클래스는 복제가 가능하다라는 것을, 즉 Object.clone() 메서드를 호출할 수 있다는 신호를 jvm에 알려주게 된다.
이러한 인터페이스를 마커 인터페이스 혹은 태그 인터페이스라 한다.

또 하나 주의해야할 점은 Object의 clone() 메서드가 protected라는 것이다. 그러므로 Cloneable 인터페이스를 구현한 클래스 내에서 clone() 메서드를 오버라이드 해줘야 한다. 그렇지 않으면 다른 패키지에서 clone() 메서드를 사용할 수 없게 된다.

얕은 복사와 깊은 복사

Object의 clone() 메서드는 만약 클래스 내 객체 필드가 존재한다면 얕은 복사를 하게 된다. 즉 객체 필드의 실제 값을 복사하는 것이 아닌 해당 객체 필드의 주소값을 할당하게 된다. 아래 코드를 봐보자.

// Brand.java
public class Brand {
	private int id;
    private String name;

	public Brand(int id, String name) {
		this.id = id;
		this.name = name;
	}
}
// Product.java
public class Product implements Cloneable {

	private int productId;
	private String productName;
	private Brand brand;

	public Product(int id, String name, Brand brand)
	{
		this.productId = id;
		this.productName = name;
		this.brand = brand;
	}
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}

	//Getters and Setters
}
// TestCloning.java
public class TestCloning
{
	public static void main(String[] args) throws CloneNotSupportedException
	{
		Brand brand = new Brand(1, "lucid");
        
		Product orig = new Product(1, "luna", brand);
		Product cloned = (Product) orig.clone();
		
        cloned.getBrand().setName("moment");
        
        System.out.println(orig.getBrand().getName());
        System.out.println(cloned.getBrand().getName());
		
	}
}

결과

moment
moment

결과를 보면 알다시피 cloned의 브랜드 네임을 변경했는데 orig의 브랜드 네임도 같은 값을 출력했다. 이는 위에서 말했다시피 Object의 clone() 메서드가 디폴트로 얕은 복사를 하기 때문이다. orig의 브랜드 객체 필드의 주소값이 cloned 브랜드 객체 필드에 복사되었고 이로 인해 orig와 cloned의 브랜드 객체 필드는 메모리 상에서 같은 곳을 바라보게 되었다.

간단한 객체의 복사에는 얕은 복사로도 충분할 수 있으나 이렇게 객체가 중첩되어 필드로 있는 경우에는 원하지 않는 결과가 발생할 수 있다.

이럴 경우에 깊은 복사를 통해 문제를 해결할 수 있다.

// Product 클래스에서 clone() method 수정
@Override
protected Object clone() throws CloneNotSupportedException {
	Product cloned = (Product)super.clone();
	cloned.setBrand((Brand)cloned.getBrand().clone());
	return cloned;
}
// 브랜드 클래스에도 clone() 메서드를 정의해준다.
@Override
protected Object clone() throws CloneNotSupportedException {
	return super.clone();
}

이러한 깊은 복사를 통해 우리가 원하는 결과값을 얻을 수 있다. 이렇듯 프로그램에서 Object clone을 사용하려는 경우 mutable 필드를 관리하여 적절히 오버라이드 해야 한다.

0개의 댓글