자바 객체배열복사 | 자바 입문강좌 21

자바 객체배열복사

자바의 배열을 복사하면서 참조변수를 복사하는 얕은복사와 깊은복사를 알아봤습니다. shallow copy and deep copy 였습니다.

객체 배열에서도 동일한 원칙이 적용될 것을 알 수 있습니다. 예제를 통해 확인해 보겠습니다.

객체배열복사

얕은복사(Shallow Copy)

객체배열(array of objects)은 일반 자료형이 아니라 복합자료형인 클래스를 담은 배열을 의미합니다. 클래스는 정수형(int) 같은 기본 자료형보다는 훨씬 크고 복잡한 자료형입니다.

따라서 아이디어는 간단하지만 소스코드는 조금 길어집니다. 아래는 얕은 복사(shallow copy)의 예를 보여줍니다.

public class Main {
    public static void main(String[] args) {
        myObject[] arrayObj = new myObject[3];

        arrayObj[0] = new myObject(101, "Sam");
        arrayObj[1] = new myObject(102, "Mike");
        arrayObj[2] = new myObject(103, "Joe");

        for (int i = 0; i < arrayObj.length; i++) {
            System.out.println("arrayObj = [" + i + "]: " + arrayObj[i]);
            arrayObj[i].showInfo();
        }

        System.out.println("------------------------------------------");

        myObject[] arrayCpyObj = new myObject[3];
        arrayCpyObj = arrayObj;

        for (int i = 0; i < arrayCpyObj.length; i++) {
            System.out.println("arrayCpyObj = [" + i + "]: " + arrayCpyObj[i]);
            arrayCpyObj[i].showInfo();
        }
    }
}
class myObject{
    int id;
    String name;
    myObject(){
    }
    public myObject(int id, String name) {
        this.id = id;
        this.name = name;
    }
    public void showInfo(){
        System.out.println("(id) : " + id);
        System.out.println("(name) : " + name);
    }
}
arrayObj = [0]: com.kay.myObject@12edcd21
(id) : 101
(name) : Sam
arrayObj = [1]: com.kay.myObject@27bc2616
(id) : 102
(name) : Mike
arrayObj = [2]: com.kay.myObject@3941a79c
(id) : 103
(name) : Joe
------------------------------------------
arrayCpyObj = [0]: com.kay.myObject@12edcd21
(id) : 101
(name) : Sam
arrayCpyObj = [1]: com.kay.myObject@27bc2616
(id) : 102
(name) : Mike
arrayCpyObj = [2]: com.kay.myObject@3941a79c
(id) : 103
(name) : Joe

객체 배열을 new 키워드로 만드는 것은 동일합니다. 허나 새로운 인스턴스 생성없이 그냥 참조변수만 다른 객체배열에 할당했습니다.

여기서 눈여겨 봐야할 부분은 new 키워드를 몇번 사용했는가입니다. 객체 배열을 2개 만드는데 2개 사용하고 1번 객체배열의 객체를 생성하는데 3번 사용했습니다. 즉 객체 3개를 가리키는 2개의 참조변수가 있음을 알 수 있습니다.

참조가 두개일 뿐이지 3개의 인스턴스이기 때문에 어느 참조를 사용해서 값을 변경해도 인스턴스는 변경됩니다. 둘은 독립적이지 않습니다.

코드의 뒤쪽에 아래 내용을 붙이면 새로운 객체배열의 참조로 기존 객체배열의 인스턴스도 변한다는 것을 알 수 있습니다.

        arrayCpyObj[0].id = 201;
        arrayCpyObj[0].name = "Carcon";

        for (int i = 0; i < arrayObj.length; i++) {
            System.out.println("arrayObj = [" + i + "]: " + arrayObj[i]);
            arrayObj[i].showInfo();
        }

복잡한 듯 보이지만 결론은 명확합니다. 실제 값이 복사된 것은 없다.

그것을 shallow copy 라고 하는데 copy 라는 말의 뜻을 잘못 이해하면 한없이 미궁에 빠집니다. 그럴때는 코드를 실습하면서 이해하도록 합니다. 실습과 이론을 같이 가는게 좋습니다.

깊은 복사(Deep Copy)

이제 깊은 복사 차례입니다.

그렇다면 이전 학습 배열복사에서 본 것 처럼 System.arraycopy ( ) 함수를 사용하면 되지 않을까요? 글쎄요… 한번 해보겠습니다.

System.arraycopy(arrayObj,0,arrayCpyObj,0,3);

System.arraycopy( ) 함수

System.arraycopy 함수는 배열에서 배열로 정해진 길이만큼 복사할 수 있도록 인수를 전달할 수 있습니다.

참고로 System 은 java.lang 패키지에 속하여 JVM에 기본으로 로드하기 때문에 import 구문은 필요없습니다.

public class Main {
    public static void main(String[] args) {
        myObject[] arrayObj = new myObject[3];

        arrayObj[0] = new myObject(101, "Sam");
        arrayObj[1] = new myObject(102, "Mike");
        arrayObj[2] = new myObject(103, "Joe");

        for (int i = 0; i < arrayObj.length; i++) {
            System.out.println("arrayObj = [" + i + "]: " + arrayObj[i]);
            arrayObj[i].showInfo();
        }
        System.out.println("------------------------------------------");
        myObject[] arrayCpyObj = new myObject[5];
        System.arraycopy(arrayObj,0,arrayCpyObj,0,3);

        for (int i = 0; i < arrayCpyObj.length; i++) {
            System.out.println("arrayCpyObj = [" + i + "]: " + arrayCpyObj[i]);
            if (arrayCpyObj[i]!= null){
                arrayCpyObj[i].showInfo();
            }
        }
    }
}
class myObject{
    int id;
    String name;
    myObject(){
    }
    public myObject(int id, String name) {
        this.id = id;
        this.name = name;
    }
    public void showInfo(){
        System.out.println("(id) : " + id);
        System.out.println("(name) : " + name);
    }
}
arrayObj = [0]: com.kay.myObject@12edcd21
(id) : 101
(name) : Sam
arrayObj = [1]: com.kay.myObject@27bc2616
(id) : 102
(name) : Mike
arrayObj = [2]: com.kay.myObject@3941a79c
(id) : 103
(name) : Joe
------------------------------------------
arrayCpyObj = [0]: com.kay.myObject@12edcd21
(id) : 101
(name) : Sam
arrayCpyObj = [1]: com.kay.myObject@27bc2616
(id) : 102
(name) : Mike
arrayCpyObj = [2]: com.kay.myObject@3941a79c
(id) : 103
(name) : Joe
arrayCpyObj = [3]: null
arrayCpyObj = [4]: null

그렇습니다. 소스코드가 길어지면 읽는게 힘들어지죠.

프로그래머가 모든 코드를 다 읽고 이해해야 하는건 맞지만, 스피드가 좀 필요할 때는 모든 코드를 다 읽으려고 하지말고 핵심을 읽도록 합니다. 사람이 코딩을 하지만 기계는 아닙니다. 그러니까 때로 융통성을 발휘할 필요도 있습니다.

위에서 읽어야 할 내용은 System.arraycopy 라고 했습니다. 인수를 뭘 줬는지 보구요. A에서 B로 복사를 하는데 이 방식은 배열의 복사에는 통했는데 객체배열의 복사에는 통하지 않습니다. 실행값의 참조변수값을 보면 두 객체배열의 알맹이가 같습니다.

결국 두 참조변수는 하나를 가리키고 있다는 말입니다.

이전 학습인 배열복사부터 지금까지 한가지의 주제를 무한 반복하는 듯한 기분이 든다면 맞습니다. 똑같은 내용을 관점을 조금씩 바꿔서 코드로 표현하고 있기 때문입니다. 지루하겠지만 이게 핵심입니다.

배열복사와 얕은 복사 깊은 복사의 차이점 구별은 메모리에 대한 모델이 머리속에 없으면 진전이 되지 않습니다. 좀 더 깊이 들어가면 그 때부터는 컴파일러 영역이 나옵니다. 허나 입문과정에서 그렇게 급발진해버리면 곤란하니까 이 정도의 내용을 다뤄봅니다.

진짜 깊은 복사(Real Deep Copy)

객체 배열의 깊은 복사를 해보겠습니다.

다소 원초적인 방식의 예제입니다.

public class Main {
    public static void main(String[] args) {
        myObject[] arrayObj = new myObject[3];

        arrayObj[0] = new myObject(101, "Sam");
        arrayObj[1] = new myObject(102, "Mike");
        arrayObj[2] = new myObject(103, "Joe");

        for (int i = 0; i < arrayObj.length; i++) {
            System.out.println("arrayObj = [" + i + "]: " + arrayObj[i]);
            arrayObj[i].showInfo();
        }
        System.out.println("------------------------------------------");
        myObject[] arrayCpyObj = new myObject[5];
//        System.arraycopy(arrayObj,0,arrayCpyObj,0,3);
//        arrayCpyObj = Arrays.copyOf(arrayObj, arrayObj.length);

        arrayCpyObj[0] = new myObject();
        arrayCpyObj[1] = new myObject();
        arrayCpyObj[2] = new myObject();

        for (int i = 0; i < arrayObj.length; i++) {
            arrayCpyObj[i].id = arrayObj[i].id;
            arrayCpyObj[i].name = arrayObj[i].name;
        }
        for (int i = 0; i < arrayCpyObj.length; i++) {
            System.out.println("arrayCpyObj = [" + i + "]: " + arrayCpyObj[i]);
            if (arrayCpyObj[i]!= null){
                arrayCpyObj[i].showInfo();
            }
        }
    }
}
arrayObj = [0]: com.kay.myObject@12edcd21
(id) : 101
(name) : Sam
arrayObj = [1]: com.kay.myObject@27bc2616
(id) : 102
(name) : Mike
arrayObj = [2]: com.kay.myObject@3941a79c
(id) : 103
(name) : Joe
------------------------------------------
arrayCpyObj = [0]: com.kay.myObject@506e1b77
(id) : 101
(name) : Sam
arrayCpyObj = [1]: com.kay.myObject@4fca772d
(id) : 102
(name) : Mike
arrayCpyObj = [2]: com.kay.myObject@9807454
(id) : 103
(name) : Joe
arrayCpyObj = [3]: null
arrayCpyObj = [4]: null

com.kay.myObject@ 하고 번호가 달라졌습니다. 주소가 달라진겁니다. 일일히 new 키워드를 사용해서 메모리를 할당하고 각각의 요소들을 복사했습니다.

자바에는 객체를 deep copy 해주는 내장된 유틸 클래스가 없습니다. 필요에 따라 직접 만들어야 합니다.

자바가 그래도 쉽다고 하는데 이런 구석이 의외로 어려운 부분이죠. C, C++과는 또 다른 측면입니다.

요약

자바에서도 조금 어려운 주제인 객체배열복사에 대한 내용을 다뤘습니다.

중급주제니까 입문단계에서는 너무 매달려 있지 마시길 바랍니다. 단지 앞으로 좀더 이해도가 필요한 부분이다는 정도만 알아도 충분합니다.

외부참조문서

자바 객체배열복사 How to Make a Deep Copy of an Object in Java | Baeldung

자바 객체배열복사 How do I copy an object in Java? – Stack Overflow

자바 객체 배열(Array of Objects) 자바 강좌 6-3 (tistory.com)

2 thoughts on “자바 객체배열복사 | 자바 입문강좌 21”

Leave a Comment