자바(Java) 에 대해 공부하면서 다음과 같은 말을 많이 들어보았을 것입니다.

 

깊은 복사(deep copy) 와 얕은 복사(shallow copy)

참조 복사(call by reference) 와 값 복사(call by value)

 

값을 다른 곳에 복사하여 사용할 때, 복사가 이루어지는 방법에 따라 동작에 많은 영향을 미치게 됩니다.

지금부터 ArrayList 를 사용하여 두 가지 방법이 어떤 차이점을 가지고 있는지 알아보도록 하겠습니다.

 

 

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
import java.util.ArrayList;
 
public class CopyTest {
    static ArrayList<String> source = new ArrayList<String>();
    static ArrayList<String> destination = new ArrayList<String>();
    
    public static void initArrayList() {
        if (null != source) {
            source.clear();
            source.add("apple");
            source.add("banana");
            source.add("cherry");
        }
 
        if (null != destination) {
            destination.clear();
        }
    }
    
    public static void printArrayList() {
        System.out.println("====== source result ======");
        for (int i = 0; i < source.size(); i++) {
            System.out.println("source ["+i+"] : " +source.get(i));
        }
 
        System.out.println("====== destination result ======");
        for (int i = 0; i < destination.size(); i++) {
            System.out.println("destination ["+i+"] : " +destination.get(i));
        }
    }
 
    public static void main(String[] args) {
        initArrayList();
 
        // TODO : operation
        
        printArrayList();
    }
}
 
cs

 

 

코드를 간단히 살펴보면,  2 개의 ArrayList 가 선언되어 있고(source, destination)

main() 메소드의 // TODO : operation  위치에서 다음과 같은 2가지 동작을 수행한 후

각각의 ArrayList 가 가지고 있는 값을 모두 출력하게 됩니다. 

1. source 에 있는 내용을 destination 에 복사

2. destination 에 항목을 1개 추가

 

 

동일한 동작을 얕은 복사와 깊은 복사로 적용해 보았습니다.

1. 얕은 복사(shallow copy)      

destination = source;

destination.add("kiwi");

  

<결과>

destination 에만 추가한 kiwi 가 source 에도 추가되어 있음을 확인할 수 있습니다.

shallow copy 는 원본과 복사본 둘 중 한쪽의 수정이 양쪽에 모두 영향을 미치게 됩니다.

 

====== source result ======
source [0] : apple
source [1] : banana
source [2] : cherry
source [3] : kiwi 
====== destination result ======
destination [0] : apple
destination [1] : banana
destination [2] : cherry
destination [3] : kiwi

 

2. 깊은 복사(deep copy)      

destination.addAll(source) ; // 또는 destination =(ArrayList<String>)source.clone();

destination.add("kiwi");

  

<결과>

destination 에만 추가한 kiwi 가 source 에는 존재하지 않음을 확인할 수 있습니다.

deep copy 는 원본과 복사본 둘 중 한쪽의 수정이 다른 한쪽에 영향을 미치지 않습니다.

주석으로 표기한 방법으로도 같은 효과를 얻을 수 있습니다.

 

====== source result ======
source [0] : apple
source [1] : banana
source [2] : cherry
====== destination result ======
destination [0] : apple
destination [1] : banana
destination [2] : cherry
destination [3] : kiwi

 

 

 

그런데 여기서 추가적으로 1가지 더 살펴 보아야 하는 부분이 있습니다.

ArrayList  의 Item 으로 객체가(Object) 선언되어 있다면 깊은 복사는 어떻게 동작하게 될까요?

 

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
55
56
57
58
59
60
61
62
63
64
import java.util.ArrayList;
 
class Fruit {
    private String name;
    private int count;
 
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getCount() {
        return count;
    }
    public void setCount(int count) {
        this.count = count;
    }
}
 
public class CopyTest {
    static ArrayList<Fruit> source = new ArrayList<Fruit>();
    static ArrayList<Fruit> destination = new ArrayList<Fruit>();
    
    public static void initArrayList() {
        if (null != source) {
            source.clear();
            
            String[] names = new String[] {"apple""banana""cherry"};
            for (int i = 0; i < names.length; i++) {
                Fruit f = new Fruit();
                f.setName(names[i]);
                f.setCount(i + 1);
                source.add(f);
            }
        }
 
        if (null != destination) {
            destination.clear();
        }
    }
    
    public static void printArrayList() {
        System.out.println("====== source result ======");
        for (int i = 0; i < source.size(); i++) {
            System.out.println("source["+i+"] name:"+source.get(i).getName());
            System.out.println("source["+i+"] count:"+source.get(i).getCount());
        }
 
        System.out.println("====== destination result ======");
        for (int i = 0; i < destination.size(); i++) {
            System.out.println("destination["+i+"] name:"+destination.get(i).getName());
            System.out.println("destination["+i+"] count:"+destination.get(i).getCount());
        }
    }
 
    public static void main(String[] args) {
        initArrayList();
        
         // TODO : operation
 
        printArrayList();
    }
}
cs

 

코드를 간단히 살펴보면, name 과 count 를 속성으로 갖고 있는 Fruit 클래스가

2 개의 ArrayList 의(source, destination) Item 으로 선언되어 있습니다.

마찬가지로 main() 메소드의 // TODO : operation  위치에서 다음과 같은 2가지 동작을 수행한 후

각각의 ArrayList 가 가지고 있는 값을 모두 출력하게 됩니다. 

1. source 에 있는 내용을 destination 에 복사

2. destination 에 항목을 1개 추가

 

 

동일한 동작을 얕은 복사와 깊은 복사로 적용해 보았습니다.

1. 얕은 복사(shallow copy)      

destination = source;

Fruit f = new Fruit();

f.setName("kiwi");

f.setCount(4);

destination.add(f);

  

<결과>

destination 에만 추가한 kiwi 가 source 에도 추가되어 있음을 확인할 수 있습니다.

shallow copy 는 원본과 복사본 둘 중 한쪽의 수정이 양쪽에 모두 영향을 미치게 됩니다.

 

====== source result ======
source [0] name : apple
source [0] count : 1
source [1] name : banana
source [1] count : 2
source [2] name : cherry
source [2] count : 3
source [3] name : kiwi
source [3] count : 4
====== destination result ======
destination [0] name : apple
destination [0] count : 1
destination [1] name : banana
destination [1] count : 2
destination [2] name : cherry
destination [2] count : 3
destination [3] name : kiwi
destination [3] count : 4

 

2. 깊은 복사(deep copy)     

destination.addAll(source) ; // 또는 destination =(ArrayList<Fruit>)source.clone();

Fruit f = new Fruit();

f.setName("kiwi");

f.setCount(4);

destination.add(f);

  

<결과>

destination 에만 추가한 kiwi 가 source 에는 존재하지 않음을 확인할 수 있습니다.

deep copy 는 원본과 복사본 둘 중 한쪽의 수정이 다른 한쪽에 영향을 미치지 않습니다.

주석으로 표기한 방법으로도 같은 효과를 얻을 수 있습니다.

 

====== source result ======
source [0] name : apple
source [0] count : 1
source [1] name : banana
source [1] count : 2
source [2] name : cherry
source [2] count : 3
====== destination result ======
destination [0] name : apple
destination [0] count : 1
destination [1] name : banana
destination [1] count : 2
destination [2] name : cherry
destination [2] count : 3
destination [3] name : kiwi
destination [3] count : 4

 

 

이것만으로 정말 제대로 된 Fruit 객체에 대한 깊은 복사가 이루어 진 것일까요?

source 의 apple 객체의 count 를 1 에서 10 으로 값을 바꾼다면 destination 에는 영향이 없을까요?

다시 테스트를 진행해 보도록 하겠습니다.

 

* 객체의 깊은 복사(deep copy)

destination.addAll(source) ; // 또는 destination =(ArrayList<Fruit>)source.clone();

Fruit f = destination.get(0);    // "apple"

f.setCount(10);                    // change : 1 -> 10

  

<결과>

분명히 깊은 복사를 진행했는데도 source 와 destination 의 apple 의 count 가 모두 10으로 변경된 것을 확인할 수 있습니다. 결국 두 ArrayList 의 포함된 Fruit 은 같은 객체라는 의미입니다.

 

====== source result ======
source [0] name : apple
source [0] count : 10
source [1] name : banana
source [1] count : 2
source [2] name : cherry
source [2] count : 3
====== destination result ======
destination [0] name : apple
destination [0] count : 10
destination [1] name : banana
destination [1] count : 2
destination [2] name : cherry
destination [2] count : 3

 

이 문제를 해결하기 위해서는 결국 다음과 같은 방법이 필요합니다.

 

* 객체의 깊은 복사(deep copy)

1. Fruit 클래스의 복사 생성자 추가

public Fruit() { }      // 기본 생성자

public Fruit(Fruit f) { // 복사 생성자

   this.name = f.getName();
   this.count = f.getCount();

 }

 

2. main() 메소드에서 객체에 대한 깊은 복사(deep copy) 수행

for (int i = 0; i < source.size(); i++) {
        destination.add(new Fruit(source.get(i)));
}
Fruit f2 = destination.get(0);    // "apple"
f2.setCount(10);                    // change : 1 -> 10

  

<결과>

원본과 복사본 둘 중 한쪽의 수정이 다른 한쪽에 영향을 미치지 않습니다.

이제서야 완전히 독립된 ArrayList 객체 복사가 완료되었습니다.

 

====== source result ======
source [0] name : apple
source [0] count : 1
source [1] name : banana
source [1] count : 2
source [2] name : cherry
source [2] count : 3
====== destination result ======
destination [0] name : apple
destination [0] count : 10
destination [1] name : banana
destination [1] count : 2
destination [2] name : cherry
destination [2] count : 3

 

Posted by maze1008

댓글을 달아 주세요

  1. Jubeat 2016.09.26 14:35 신고  댓글주소  수정/삭제  댓글쓰기

    퍼갈게요~

  2. 에브리플라워 2018.07.13 15:52 신고  댓글주소  수정/삭제  댓글쓰기

    깔끔하게 정리해주셔서 테스트를 덜하게 되었습니다. 감사해요 ㅎ