본문 바로가기
Develop/Java

[Java] 얕은 복사와 깊은 복사 (+Clone)

by 연로그 2021. 6. 22.
반응형

코딩테스트를 보면서 Queue를 여러 메소드에서 사용하다가 요소가 여러개 삭제되는 경우가 있었다.

파라미터로 보낸 Queue에서 요소를 삭제하면 원본 Queue에도 영향을 미치는 문제였으며, String 같은 변수를 넘길 때는 파라미터 값을 변경해도 원본에는 영향을 미치지 않았다.

이 개념을 정리하기 위해 글을 작성한다.

 


한 가지 간단한 테스트를 해보자.

public class Test {
	public static void main(String[] args) {
		String str = "originTest";
		String[] arr = {"origin", "test"};
		
		System.out.println("before: "+str);
		for(String a:arr) {
			System.out.println("before: "+a);
		}
		
		copyTest(str,arr);
		
		System.out.println("after: "+str);
		for(String a:arr) {
			System.out.println("after: "+a);
		}
	}
	
	public static void copyTest(String str, String[] arr) {
		str = "copyTest";
		arr[0] = "copy";
	}
}

위 코드는 다음과 같은 작업을 수행한다.

  1. String과 String[] 선언 및 초기화
  2. String과 String[] 내의 요소들 출력
  3. copyTest에 String과 String[]을 파라미터로 보낸 뒤, 값 변경
  4. String과 String[] 내의 요소들 다시 출력

 

String은 타 메소드에서 변경을 해도 그대로 출력되는데,

String[]은 타 메소드에서 변경된 값이 출력되는 것을 확인할 수 있다.

왜 이런걸까? 를 알기 위해선 우선 얕은 복사깊은 복사에 대한 개념을 알아야 한다.

 

 얕은 복사; Shallow Copy

  • '값'이 아닌 '주소 값'을 복사
  • 복사한 후에 원본 값이 변경되면 복사한 값도 변경됨
  • = 연산자는 얕은 복사를 수행

 깊은 복사; Deep Copy

  • '값'을 새로운 메모리 공간에 복사
  • 복사한 후에 원본 값이 변경되어도 복사한 값은 유지
  • 얕은 복사에 비해 메모리 소요가 크고 속도가 느림

 

기본 자료형 변수가 파라미터로 던져지면 을 던지지만,

Array, List 등의 변수가 파라미터로 던져지면 주소를 던진다.

전자는 깊은 복사, 후자는 얕은 복사로 이해하면 된다.

(Call by Value, Call by Reference을 구글링하면 더 많은 정보를 볼 수 있다.)

 

그렇다면 Array나 List 등을 깊은 복사를 하고 싶으면 다음과 같은 방법을 선택하면 된다.

  1. 원본 변수에 대해 요소 하나하나를 꺼내서 복사 변수에 넣기
  2. Object의 clone 메소드 이용

 

+ 참고로 List에서 제공하는 Collections.copy()나 addAll() 같은 메소드들은 Shallow Copy다


Clone

  • 원본 객체의 필드 값과 동일한 값을 갖는 새로운 객체 생성
  • 원본 객체를 안전하게 보호하기 위해 사용
  • 깊은 복사를 위해서는 Cloneable 인터페이스를 implement하여 clone() 메소드 재정의

 

기본적으로 clone()을 그냥 이용하면 shallow copy가 진행된다.

+ 예외로 Array의 경우에는 clone() 메소드를 바로 사용해도 deep copy가 된다.

+ Arrays.copyOf(), copyOfRange() 메소드도 deep copy.

 

하지만 clone()를 오버라이딩하면 deep copy를 할 수 있다.

이를 위해서는 3가지 조건이 필요하다.

  1. Cloneable 인터페이스를 implements하기
  2. clone() 메소드를 override하기
  3. try - catch 구문을 이용해 CloneNotSupportedException 던지기

 

Cloneable 인터페이스란? ▼

더보기

Cloneable interface

  • 해당 인터페이스를 구현함으로써 해당 메소드가 해당 클래스의 인스턴스에 대한 필드별 복사본을 만드는 것을 가능하도록 Object.clone() 메소드에 표시
  • 해당 인터페이스를 구현하지 않은 인스턴스에서 Object.clone() 호출 시 CloneNotSupportedException 발생
  • clone() 메소드를 포함하지 않음

 

CloneNotSupportedException이란? ▼

더보기

CloneNotSupportedException

  • Cloneable 인터페이스를 구현하지 않을 경우에 발생하는 예외
  • implements Cloneable을 했으므로 발생하지 않겠지만 checked exception이므로 반드시 try-catch 처리를 해줘야 한다.

 

예제

clone()을 오버라이딩 해볼 간단한 TestClass를 작성해보았다.

public class TestClass implements Cloneable{
    private String id;
    ...
    @Override
    public Object clone() {
        Object obj = null;
        try {
            obj = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return obj;
    }
}

(생략된 부분에는 생성자와 getter, settter 메소드 정도만 선언해두었다.)

 

이제 clone() 메소드를 실행시켜보자.

public class Test {
    public static void main(String[] args) {
        TestClass test1 = new TestClass("1");
        TestClass test2 = test1;
        test2.setId("2");
        TestClass test3 = (TestClass) test1.clone();
        test3.setId("3");
		
        System.out.println("test1: " + test1.getId());
        System.out.println("test2: " + test2.getId());
        System.out.println("test3: " + test3.getId());
    }
}

확인 결과 = 연산자로 복사한 test2는 shallow copy가,

clone() 메소드로 복사한 test3는 deep copy가 이루어진 것을 확인할 수 있다.

 

하지만 clone() 메소드를 오버라이딩 할 때는 유의할 점이 있어 오버라이딩을 권장하지 않는 사람들이 많다.

이 부분에 대해서는 나중에 포스팅 하겠다.


참조

1. https://jyosssss.tistory.com/78

2. https://developer-youngjun.tistory.com/20

3. https://atoz-develop.tistory.com/entry/JAVA-List-%EA%B0%9D%EC%B2%B4-%EB%B3%B5%EC%82%AC-%EB%B0%A9%EB%B2%95%EA%B3%BC-Collections-copy

4. https://docs.oracle.com/javase/7/docs/api/java/lang/Cloneable.html

5. https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#clone()

 

 

반응형

'Develop > Java' 카테고리의 다른 글

[Java] BufferedReader, BufferedWriter  (0) 2021.10.04
[Jackson] JsonNode, ObjectNode, ArrayNode 차이  (4) 2021.08.24
[Java] static과 synchronized  (0) 2021.05.26
[Java] Collections와 Map  (1) 2021.05.21
[MVC] 어댑터 추가  (0) 2021.05.19