• 깊은 복사(Deep Copy)와 얕은 복사(Shallow Copy)의 이해

    2021. 3. 15.

    by. 웰시코더

    반응형

    -자바에서 객체를 복사하는 두 가지 방법에 대해 알아본다.


    디자인패턴 중 프로토타입 패턴을 학습하면서 깊은 복사, 얕은 복사의 개념이 나왔다. 이 기회에 제대로 알아볼 생각으로 글을 쓴다. 대충 알고 있었는데 예시를 작성하며 내부 동작 등을 제대로 파악해보려고 한다.

    얕은 복사와 깊은 복사의 개념

    얕은 복사(Shallow Copy)란 객체의 실제 값이 아닌 참조값(주소값)을 복사하는 것이다. 반면 깊은 복사(Deep Copy)란 참조값이 아닌 인스턴스를 새로 복사하여 아예 실제값을 복사하는 것이다. 밑의 예제로 설명하는 것이 더 이해가 쉽기 때문에 바로 예제로 들어간다.

    얕은 복사(Shallow Copy)

    우선 Fruit이라는 클래스를 다음과 같이 만든다.

    Fruit.java

    public class Fruit {
    	private String fruit;
    
    	public void setFruit(String fruit) {
    		this.fruit = fruit;
    	}
    	
    	public String getFruit() {
    		return fruit;
    	}
    }
    


    우선 예제 인스턴스를 생성해보자.

    CopyTest.java

    public class CopyTest {
    	public static void main(String[] args) {
    		Fruit fruit = new Fruit();
    	}
    }
    


    new Fruit()을 통해 인스턴스가 생성되고 이 인스턴스는 Heap 메모리에 할당된다. 그리고 fruit 변수는 이 인스턴스의 주소값을 참조하게 된다. 여기서 이 인스턴스를 다시 생성하지 않고 복사하기 위해 다음과 같이 해보자.

    Fruit fruit = new Fruit();
    Fruit copiedFruit = fruit;


    이렇게 하면 정상적인 복사가 이뤄질까? 복사가 되지만 완벽한 복사가 아닌 얕은 복사(Shallow Copy)가 된다. 얕은 복사란 객체의 실제 값이 아닌 참조값(주소값)을 복사하는 것이다. 즉, copiedFruit도 fruit이 참조하는 new Fruit() 객체를 바라보고 있는 것이다. 이런 경우 copiedFruit 내부 변수 등을 수정하면 fruit도 그대로 영향을 받는다.

    Fruit fruit = new Fruit();
    Fruit copiedFruit = fruit;
    
    copiedFruit.setFruit("APPLE");
    
    System.out.println(fruit.getFruit());	//APPLE


    얕은복사가 되었는지 어떻게 확인할 수 있을까? copiedFruit의 setFruit()을 이용해 "APPLE"값을 넣어 보자. 이후 copiedFruit이 아닌 fruit의 getFruit()을 출력해보면 "APPLE"이 출력된다. copiedFruit을 수정했음에도 fruit이 수정되는 것이다. 이는 같은 주소값을 갖고 있기 때문이다. 주소값을 한 번 출력해보자.

    System.out.println(fruit); //Fruit@7852e922
    System.out.println(copiedFruit); //Fruit@7852e922

    깊은 복사(Deep Copy)

    이를 해결하기 위해 깊은 복사(Deep Copy)를 해야하는데 자바에서는 Clonable 인터페이스를 구현하여 깊은 복사를 할 수 있다. 깊은 복사를 하면 참조값이 아닌 인스턴스 자체가 새로 복사되어 메모리에 올라간다. 다음과 같이 Clonable 인터페이스를 구현 후 clone()을 오버라이드 하자.

    Fruit.java

    public class Fruit implements Cloneable{
    	private String fruit;
    
    	public void setFruit(String fruit) {
    		this.fruit = fruit;
    	}
    	
    	public String getFruit() {
    		return fruit;
    	}
    	
    	@Override
    	protected Object clone() throws CloneNotSupportedException {
    		return super.clone();
    	}
    }
    


    그리고 깊은 복사(Deep Copy)를 다음과 같이 한다.

    CopyTest.java

    public class CopyTest {
    	public static void main(String[] args) throws CloneNotSupportedException {
    		Fruit fruit = new Fruit();
    		Fruit copiedFruit = fruit;
    		
    		/**
    		 * Deep Copy test
    		 */
    		Fruit deepCopiedFruit = (Fruit) fruit.clone();	//override한 clone 메소드를 사용하며 복사 
    		
    		//fruit과 deepCopiedFruit 주소값이 다름을 확인할 수 있다.
    		System.out.println(fruit);	//Fruit@7852e922
    		System.out.println(deepCopiedFruit);	//Fruit@4e25154f
    		
    	}
    }
    


    그리고 CopyTest 클래스에서 다음과 같이 fruit.setFruit("PINEAPPLE")을 사용해서 값 변경 후, deepCopiedFruit.getFruit()을 호출해보자. deepCopiedFruit은 기존에 복사했던 값 APPLE이 유지되어 있음을 확인할 수 있다.

    fruit.setFruit("PINEAPPLE");
    System.out.println(fruit.getFruit());	//PINEAPPLE
    System.out.println(deepCopiedFruit.getFruit());	//APPLE




    출처 : rok93.tistory.com/entry/%EC%96%95%EC%9D%80%EB%B3%B5%EC%82%AC-VS-%EA%B9%8A%EC%9D%80%EB%B3%B5%EC%82%AC

    반응형

    댓글