본문으로 바로가기

Call by value 와 Call by reference에 대하여

category Java 2021. 1. 29. 15:53

Call by value 와 Call by reference에 대하여

프로그래밍 언어를 공부할때 가장 기본적이면서도 중요한 개념 중 하나가 바로 call by value와 call by reference이다. 자바의 기초를 다루는 기본 서적에서도 비교적 앞부분에 해당 내용이 등장하곤 한다.

또한 가장 기본적이면서도 중요한 내용이지만, 두 개념에 대한 이해가 잘 정리되지 않아서 착각하고 있는 경우가 종종 있는 것 같다.

이번 기회에 확실히 정리하고 넘어가자.

Call by value 와 Call by Reference의 정의

call by value란 우리말 그대로 해석하면 값에 의한 호출이다. 값을 복사하거나, 메서드의 파라미터로 전달할때 복사한 값이나 전달한 값을 변경하여도 원본은 변경되지 않는다.

 

call by reference란 우리말 그대로 해석하면 참조에 의한 호출이다. 전달받은 값을 직접 참조하기때문에 해당 값을 변경할 경우 원본의 값도 변경된다.

 

많은 사람들이 Primitive type(기본 자료형)이 아닌 객체 참조 변수를 복사하거나 메소드 파라미터로 넘기는 경우를 Call by Reference로 착각하고 있는 경우가 있다.

 

결론부터 말하자면 자바에는 Call by reference가 존재하지 않는다.

 

쉽게 이해할 수 있도록 아래 예제 코드와 그림을 보면서 이해해보자.

  1. 기본 자료형을 메소드의 인자값으로 사용했을때
public class Main {
    public static void main(String[] args) {

        int value1 = 1;
        int value2 = 2;

        System.out.println("swap 메소드 실행 전");
        System.out.println("value1 : " + value1);
        System.out.println("value2 : " + value2);

        swap(value1,value2);

        System.out.println("swap 메소드 실행 후");
        System.out.println("value1 : " + value1);
        System.out.println("value2 : " + value2);
    }

    private static void swap(int arg1, int arg2) {
        int temp = arg1;
        arg1 = arg2;
        arg2 = temp;
    }
}
swap 메소드 실행 전
value1 : 1
value2 : 2
swap 메소드 실행 후
value1 : 1
value2 : 2

위에서 설명한 Call by value의 개념처럼 기본 자료형의 값을 메소드 파라미터로 넘겨서 사용했기 때문에 원본이 변경되지 않았다.

단지 swap 메소드 내에 존재하는 arg1과 arg2의 값만 변경되었을 뿐이다.

 

다음으로 swap 메소드의 파라미터로 객체를 전달했을때 결과를 살펴보자.

 

  1. 객체를 인자값으로 사용했을 때
package callbyvalue;

class Value {
    int number;

    Value(int number){
        this.number = number;
    }
}

public class Main {

    public static void main(String[] args) {
        Value value1 = new Value(1);
        Value value2 = new Value(2);

        System.out.println("swap 메소드 실행 전");
        System.out.println("value1.number : " + value1.number);
        System.out.println("value2.number : " + value2.number);

        swap(value1,value2);

        System.out.println("swap 메소드 실행 후");
        System.out.println("value1.number : " + value1.number);
        System.out.println("value2.number : " + value2.number);
    }

   private static void swap(Value arg1, Value arg2) {
        int temp = arg1.number;
        arg1.number = arg2.number;
        arg2.number = temp;
    }
}
swap 메소드 실행 전
value1.number : 1
value2.number : 2
swap 메소드 실행 후
value1.number : 2
value2.number : 1

결과값을 보면 swap 메소드 실행 전과 후의 결과가 다르다.

이 결과를 보고 call by reference가 일어낫다고 생각할 수도 있지만, 이 과정도 결국에는 call by value다.

아래 JVM 메모리구조 그림을 통해서 무슨 말인지 이해해보자.

위 메모리 구조는 main 메소드가 swap 메소드를 호출하고 swap 메소드가 아직 실행되기 전 까지 메모리 구조이다.

main 메소드에 할당되어있는 value1과 swap 메소드에 할당되어있는 arg1는 각각 동일한 객체의 주소값을 참조하고 있다.

value2와 arg2도 마찬가지로 동일한 객체의 주소값을 참조하고 있다.

 

이제 swap 메소드가 실행되었다. 메모리 구조는 다음과 같이 변경된다.

swap 메소드를 실행한 후 메모리 구조의 변경점은 value1과 arg1, 그리고 value2와 arg2가 참조하고있는 객체의 내용이 변경 되었다는 점이다.

즉 swap 메소드의 arg1과 arg2 변수는 main 메소드의 value1과 value2에게 복사 받아 동일한 객체의 주소값을 참조하고 있을 뿐 변경된 것은 참조하고 있는 객체의 내용이라는 것이다.

이렇게 참조 되어지는 값이 변경되는것이 마치 call by reference가 일어난것과 같은 착각을 불러오는 것이다.

 

아직 이해가 부족한 것 같으니 아래 예시를 하나 더 살펴보자.

package callbyvalue;

class Value {
    int number;

    Value(int number){
        this.number = number;
    }
}

public class Main {

    public static void main(String[] args) {
        Value value1 = new Value(1);
        Value value2 = new Value(2);

        System.out.println("swap 메소드 실행 전");
        System.out.println("value1.number : " + value1.number);
        System.out.println("value2.number : " + value2.number);

        swap(value1,value2);

        System.out.println("swap 메소드 실행 후");
        System.out.println("value1.number : " + value1.number);
        System.out.println("value2.number : " + value2.number);
    }

    private static void swap(Value arg1, Value arg2) {
        int temp = arg1.number;
        arg1.number = arg2.number;
        arg2.number = temp;

        arg2 = new Value(30);

    }

}

swap 메소드에서 arg2의 값을 새로운 Value 객체를 참조하도록 코드를 추가해보자.

만약 자바가 call by reference라면 main메소드에 존재하는 value2.number의 값도 30으로 출력되어야 할 것이다.

swap 메소드 실행 전
value1.number : 1
value2.number : 2
swap 메소드 실행 후
value1.number : 2
value2.number : 1

하지만 실행결과는 변하지 않았다.

swap 메소드를 실행한 후의 JVM 메모리 구조는 아래와 같다.

정리

자바에는 Call by reference가 존재하지 않는다.

기본 자료형 변수는 저장하고 있는 값을 그 값 자체로 해석하는 반면에, 참조 변수는 저장하고 있는 값을 주소로 해석한다는 차이가 존재할 뿐이다. 즉, 값의 래퍼런스(참조)가 call by value 방식으로 복사되는 것이다.