[ Flutter ] Class, List, Map 등의 복사 (깊은복사, 얕은복사)
옛날에 한참 C, C++ 코딩할때는 포인터를 사용하는 Call by reference, Call by value 요렇게 2가지 타입 중 적절한 방법으로 자료처리를 해던 기억이 있다. 그때 당시는 주소참조방식 과 값참조방식 뭐 이렇게 이해하고 있었는데, 요즘은 이를 지칭하는 표현(단어)도 많이 바뀌어 있는 듯 하다( 내가 옛날사람이긴 한가 봐. ㅠㅠ). 플러터나 자바쪽에서는 깊은복사 와 얕은복사라는 표현을 주로 사용하고 있던데 개인적으로는 직관적이지 않아 아직도 헷갈리는 단어다. 그래서 요 부분에 대한 개인적인 정리 차원에서 블로그에 남겨두고자 한다.
Flutter(DART)에서 class, list, map 같은 객체(Object)를 다루다가 값이 이상하다는 생각이 들 때가 있다. 필자의 경우 코딩하면서 잠깐 복사해둔 리스트의 내용이 자꾸 바뀌기에 멀쩡한 다른 코드만 마구마구 변경하는 삽질의 시간을 보내고 난 후 알았다.
List 복사는 Call by value 가 아니라 Call by reference 가 기본이라는 사실을.....
깊은복사(Deep copy) : 복사할 객체의 값(Value)를 복사
얕은복사(Shallow copy) : 복사할 객체가 저장되어 있는 참조값(주소 reference or address)을 복사
아마도 얕은복사라는 의미는 객체의 크기에는 상관없이 객체가 저장되어 있는 메모리의 시작 주소값 만을 가볍게 가져오기 때문에 그런것 같고, 같은 의미로 깊은 복사라 하면 객체의 크기만큼 똑같은 크기의 객체를 하나 더 만든다는 의미가 아닐까? 필자가 사용해왔던 몇가지 프로그래밍 언어들은 주로 깊은 복사( Call by value )가 기본이었던 것 같다. C 또는 Pascal 의 경우 얕은복사(Call by reference)를 위해서는 포인터연산자(*)를 써야만 했다.
하지만 플러터의 경우 String, 숫자형, bool, null 과 같은 기본데이터타입(단일형)을 제외한 모든 객체의 복사는 Call by reference 가 기본인 듯 하다. (Class, List, Map 등)
예제 화면에 출력값을 잘 살펴보면 Call by reference 와 Call by value 의 차이점을 알 수 있을 것이다.
그리고 코드에 보면 [...list1] 이렇게 코딩되어 있는 부분을 볼 수 있다. Dot 3개가 있는 이것을 스프레드(Spread) 연산자 "..." 라고 하는데 Call by value 방식의 연산자 이다. 즉 새로운 저장공간을 생성하고 저장영역을 할당 후, 객체가 가지고 있는 Value들을 그대로 복사하고 새주소를 Return 하기 때문에 원본 객체의 변화에 영향을 받지 않는 Call by value 방식에 사용되어 진다.
위에서 보면 list1 과 list2 의 HashCode 값이 동일함을 알 수 있다. 얕은복사를 통해 list1 과 list2 가 같은 메모리 주소를 가리키고 있기 때문에 List1 과 List2 어느 한쪽에서든 값을 변경하면 2개의 List가 영향을 받게 된다.