쭈쌤
쭈쌤 Hello World

[Java] 값 복사와 참조 복사

크리에이티브 커먼즈 라이선스 ITPAPER(호쌤,쭈쌤)에 의해 작성된 ≪[Java] 값 복사와 참조 복사≫은(는) 크리에이티브 커먼즈 저작자표시-비영리-동일조건변경허락 4.0 국제 라이선스에 따라 이용할 수 있습니다.
이 라이선스의 범위 이외의 이용허락을 얻기 위해서는 leekh4232@gmail.com으로 문의하십시오.

[Java] 값 복사와 참조 복사

기본 자료형 변수간의 대입과 배열간의 대입 혹은 파라미터로서의 사용은 서로 차이가 있다.

기본 자료형: char, boolean, byte, short, int, long, float, double

#01. 값 복사

기본 자료형 변수를 서로 대입하거나 파라미터로 사용할 경우의 현상.

1) 기본 자료형간의 대입

단순 복사가 발생하기 때문에 복사 후 원본이 변경되더라도 복사본에는 영향이 없다. (반대의 경우도 마찬가지)

ValueCopy.java

기본 자료형 변수간의 복사 실험

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ValueCopy {
    public static void main(String[] args) {
        int a = 10; // 원본
        int b = a;  // 복사본
        System.out.println("a=" + a);
        System.out.println("b=" + b);
        System.out.println("----------");

        a += 10;    // 원본 수정
        System.out.println("a=" + a);
        System.out.println("b=" + b);
        System.out.println("----------");

        b -= 10;    // 복사본 수정
        System.out.println("a=" + a);
        System.out.println("b=" + b);
    }
}
출력결과
1
2
3
4
5
6
7
8
a=10
b=10
----------
a=20
b=10
----------
a=20
b=0

2) 변수간의 값 복사 원리

프로그램이 동작하는 동안 하나의 변수가 선언되면 컴퓨터는 해당 변수의 데이터 타입에 맞는 공간을 RAM 안에 점유한다. 예를 들어 int형의 변수 a를 선언한다면 int의 메모리 크기는 4byte 이므로 RAM 카드 안에서 4byte에 해당하는 영역을 점유하고 그 영역에 a라는 식별 이름을 적용한다.

1
int a;

pointer1.png

선언한 변수에 값을 대입한다는 것은 점유해 둔 메모리 공간에 데이터를 기록한다는 의미이다.

1
a = 10;

pointer2.png

새로운 int형 변수 b를 선언하면 컴퓨터는 RAM 상의 무작위 위치에 4byte에 해당하는 공간을 점유한다.

메모리의 공간을 무작위로 사용한다고 해서 Random Access Memory 입니다.

1
int b;

pointer3.png

a에 저장되어 있던 값을 b에 복사한다는 것은 메모리간의 값을 서로 복제한다는 의미이다.

1
b = a;

pointer4.png

3) 기본 자료형 변수를 파라미터로 사용하는 경우

메서드가 서로 다르면 변수의 유효성 범위(스코프)가 서로 다르므로 변수 이름이 동일하더라도 서로 다른 값으로 구분된다.

아래 두 개의 메서드 중, foo()에서 정의한 파라미터 a와 변수 bbar()메서드가 정의하는 파라미터 a나 변수 b와는 단지 이름만 같을 뿐 서로 다른 값이다.

결국 파라미터의 전달도 값 복사 현상이 발생한다.

실제 생성되는 메모리의 위치가 다르기 때문에 구별됩니다.

1
2
3
4
5
6
7
public static void foo(int a) {
    int b = a + 10;
}

public static void bar(int a) {
    int b = a - 10;
}

같은 원리로 파라미터로 전달되는 변수는 값의 복사에 대한 개념이기 때문에 변수의 이름, 파라미터의 이름은 서로 아무런 연관이 없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) {
    int a = 10;
    System.out.println("main >> " + a);
    foo(a);
    System.out.println("main >> " + a);
    bar(a);
    System.out.println("main >> " + a);
}

// 이 메서드의 파라미터 a는 main의 a와는 구별되는 메모리 공간.
public static void foo(int a) {
    a += 100;
    System.out.println("foo >> " + a);
}

// main의 a가 저장하고 있는 값이 이 메서드의 x로 복사된다.
// 즉 main에서 전달하는 값은 "int x = a"의 의미
public static void bar(int x) {
    x += 200;
    System.out.println("bar >> " + x);
}
출력결과
1
2
3
4
5
main >> 10
foo >> 110
main >> 10
bar >> 210
main >> 10

결국 기본 자료형 변수를 파라미터로 전달할 경우 값 복사가 발생하기 때문에 파라미터를 전달받은 메서드 안에서 파라미터에 대한 수정이 발생하더라도 원래의 원본은 변하지 않는다.

ValueParam.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ValueParam {
    public static void main(String[] args) {
        int x = 1;
        System.out.println("before x: " + x);

        // 기본자료형을 파라미터로 전달할 때 값이 복사되므로
        // "파라미터 = x"의 의미를 갖는다.
        foo(x);

        // foo를 호출할 때 x값이 복사되어 전달되므로
        // foo 메서드 안에서 값을 수정하더라도 복사본에 대한 변경이다.
        // 그러므로 원본은 변하지 않는다.
        System.out.println("after x: " + x);
    }

    public static void foo(int x) {
        // 이 안에서의 x는 블록이 서로 다르므로 main의 x와 구별된다.
        // 그러므로 전달되는 변수의 이름과 파라미터의 이름은 아무런 연관이 없다.
        x += 100;
        System.out.println("in foo method --> " + x);
    }
}
출력결과
1
2
3
before x: 1
in foo method --> 101
after x: 1

#02. 참조 복사

배열을 서로 대입하거나 파라미터로 사용할 경우의 현상.

1) 배열간의 대입

배열간의 대입은 배열의 변수 이름에 원소들을 참조시키기 때문에 복사본을 수정할 경우 원본도 함께 수정된다. (반대의 경우도 마찬가지)

ArrayCopy.java

배열간의 복사 실험

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ArrayCopy {
    public static void main(String[] args) {
        int[] origin = {1, 2};
        int[] copy = origin;

        System.out.println("origin[0]=" + origin[0]);
        System.out.println("origin[1]=" + origin[1]);
        System.out.println("copy[0]=" + copy[0]);
        System.out.println("copy[1]=" + copy[1]);
        System.out.println("----------");

        copy[0] += 100;
        copy[1] += 200;

        System.out.println("origin[0]=" + origin[0]);
        System.out.println("origin[1]=" + origin[1]);
        System.out.println("copy[0]=" + copy[0]);
        System.out.println("copy[1]=" + copy[1]);
    }
}
출력결과
1
2
3
4
5
6
7
8
9
origin[0]=1
origin[1]=2
copy[0]=1
copy[1]=2
----------
origin[0]=101
origin[1]=202
copy[0]=101
copy[1]=202

2) 배열간의 참조 복사 원리

컴퓨터의 메모리(RAM)은 각각의 칸에 접근할 수 있는 위치값을 int 형으로 관리한다.

모든 형태의 배열은 데이터 타입이 어떤 것이건 무조건 정수형 값을 저장할 수 있는 메모리 공간을 생성한다. 즉 모든 배열 자체의 메모리 크기는 4byte 이다.

배열을 선언할 때 데이터 타입으로 명시되는 int[]라는 것은 이 변수에 정수형 배열의 위치만을 저장할 수 있게 용도를 제한한다는 의미이다.

1
int[] a;

array1.png

new 키워드를 사용하여 배열의 크기를 할당한다는 의미는 변수가 가리키는 위치부터 몇 칸으로 구성된 배열이 존재한다는 것을 의미한다.

1
a = new int[2];

즉, 위의 코드는 메모리 어딘가에 int형 변수 2개가 묶인 배열이 존재하는데 그 배열의 시작위치를 a라는 변수에 저장하겠다는 의미이다.

아래 그림과 같이 H3라는 위치값이 저장되었다면 총 8byte의 메모리 공간이 H3부터 연속적으로 사용되고 a라는 변수를 통해 H3부터 8칸을 new int가 지정한 대로 4칸씩 나누어 읽게 된다.

array2.png

1
double[] b = new double[3];

만약 위와 같이 구현되었다면 아래와 같은 의미가 되는 것이다.

  1. b라는 정수형 변수가 생성되는데 이 변수에는 double 형 배열의 위치만 저장할 수 있다.
  2. double형 배열 3칸을 어딘가에 만들고 그 시작위치를 b에 저장한다.
  3. b가 저장하고 있는 위치부터 8byte씩 3개 이므로 24칸의 메모리가 사용된다.

결국 배열 a의 0번에 값을 대입한다는 것은 a라는 변수에 저장된 메모리 위치를 찾아가서 new int에 따라 4칸씩 나누어 서 그 중 0번째 영역에 1을 넣는다는 의미가 된다.

1
2
a[0] = 1;
a[1] = 2;

array3.png

한편, 새로운 정수형 배열 b를 선언하면 배열의 위치값을 저장할 수 있는 정수형 메모리 공간을 생성한다.

1
int[] b;

array4.png

배열 b에 배열 a를 복사하면 a에 저장되어 있던 위치값인 H3를 복사한다.

1
b = a;

array5.png

결국 a의 0번째와 b의 0번째는 같은 메모리 위치를 가리킨다.

3) 배열을 파라미터로 받는 메서드

마찬가지로 참조 형태로 전달된다.

ArrayParam1.java

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
public class ArrayParam1 {
    public static void main(String[] args) {
        int[] origin = {1, 2};
        System.out.println("origin[0]=" + origin[0]);
        System.out.println("origin[1]=" + origin[1]);
        System.out.println("----------");

        // 배열을 파라미터로 전달.
        foo(origin);
        System.out.println("origin[0]=" + origin[0]);
        System.out.println("origin[1]=" + origin[1]);
    }

    public static void foo(int[] copy) {
        // 배열 파라미터 역시 참조 형태로 전달되므로,
        // 이 메서드 안에서 배열의 원소를 변경하면
        // main에 있는 원본까지 함께 변경된다.
        for (int i=0; i<copy.length; i++) {
            copy[i] += 100;
        }
        System.out.println("copy[0]=" + copy[0]);
        System.out.println("copy[1]=" + copy[1]);
        System.out.println("----------");
    }
}
출력결과
1
2
3
4
5
6
7
8
origin[0]=1
origin[1]=2
----------
copy[0]=101
copy[1]=102
----------
origin[0]=101
origin[1]=102

4) 배열을 값복사 형태로 처리하는 방법

배열의 각 원소를 하나하나 복사해야 한다.

ArrayCopy2.java

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
public class ArrayCopy2 {
    public static void main(String[] args) {
        int[] origin = {1, 2};

        // 원본과 동일한 사이즈의 배열 생성
        int[] copy = new int[origin.length];

        // 각각의 원소를 개별적으로 복사해야 한다.
        copy[0] = origin[0];
        copy[1] = origin[1];

        System.out.println("origin[0]=" + origin[0]);
        System.out.println("origin[1]=" + origin[1]);
        System.out.println("copy[0]=" + copy[0]);
        System.out.println("copy[1]=" + copy[1]);
        System.out.println("----------");

        // 복사본을 수정하더라도 원본의 변화가 없다.
        copy[0] += 100;
        copy[1] += 200;

        System.out.println("origin[0]=" + origin[0]);
        System.out.println("origin[1]=" + origin[1]);
        System.out.println("copy[0]=" + copy[0]);
        System.out.println("copy[1]=" + copy[1]);
    }
}

자바에서 제공하는 기능을 사용할 수 도 있다.

1
2
System.arraycopy(원본배열, 원본의 복사 시작 위치,
                 복사될 배열, 복사가 시작될 위치, 복사할 값의 길이);

ArrayCopy3.java

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
public class ArrayCopy3 {
    public static void main(String[] args) {
        int[] origin = {1, 2, 3, 4, 5};
        // 원본과 동일한 사이즈의 배열 생성
        int[] copy = new int[origin.length];

        // origin의 0번째 부터 copy의 1번째에 3칸을 복사
        System.arraycopy(origin, 0, copy, 1, 3);

        for (int i=0; i<origin.length; i++) {
            System.out.printf("origin[%d]=%d \t copy[%d]=%d\n",
                                i, origin[i], i, copy[i]);
        }

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

        // 주로 다음과 같이 사용됨
        System.arraycopy(origin, 0, copy, 0, origin.length);

        for (int i=0; i<origin.length; i++) {
            System.out.printf("origin[%d]=%d \t copy[%d]=%d\n",
                                i, origin[i], i, copy[i]);
        }
    }
}
출력결과
1
2
3
4
5
6
7
8
9
10
11
origin[0]=1      copy[0]=0
origin[1]=2      copy[1]=1
origin[2]=3      copy[2]=2
origin[3]=4      copy[3]=3
origin[4]=5      copy[4]=0
-----------
origin[0]=1      copy[0]=1
origin[1]=2      copy[1]=2
origin[2]=3      copy[2]=3
origin[3]=4      copy[3]=4
origin[4]=5      copy[4]=5

참고

다른 설명에서는 다음과 같이 설명하기도 합니다.

참조복사 (Call by Reference) 값 복사 (Call by Value)
얕은복사 (Shallow Copy) 깊은복사 (Deep copy)
Rating:

크리에이티브 커먼즈 라이선스 ITPAPER(호쌤,쭈쌤)에 의해 작성된 ≪[Java] 값 복사와 참조 복사≫은(는) 크리에이티브 커먼즈 저작자표시-비영리-동일조건변경허락 4.0 국제 라이선스에 따라 이용할 수 있습니다.
이 라이선스의 범위 이외의 이용허락을 얻기 위해서는 leekh4232@gmail.com으로 문의하십시오.

comments powered by Disqus