[CS] Call by value와 Call by reference

Kim Hyen Su·2024년 5월 22일

👾 CS

목록 보기
4/5
post-thumbnail

📜 참고 포스팅

개요

프로젝트 면접 질문을 준비하면서 "Call by value"와 "Call by reference" 그리고 "Call by address" 라는 단어들에 대해서 듣게 되었습니다.

또한, Java에서는 Call by value를 사용한다고 되어 있는데 정확하게 어떻게 이걸 사용하고 있는지에 대해 알아보고 위의 단어들에 대한 의미를 이해하는 시간을 가져보려 합니다.

Call by value


메서드를 호출하는 호출자(Caller) 내 변수와 수신자(Callee)에서 받은 인자(변수)는 서로 다른 변수 입니다.

즉, Caller에서 메서드 호출 시 넣어준 변수가 복사되어 Callee 안에서 실행되므로 원본 데이터의 값이 변경되지 않습니다.

Call by reference


해당 개념은 C언어에서 포인터 개념과 연관된 내용입니다.

메서드를 호출하는 호출자(Caller)에서 변수의 참조값을 직접 전달하기 때문에 수신자(Callee)에서 받은 인자(변수)는 동일한 변수 입니다.

즉, Caller에서 메서드 호출 시 넣어준 변수는 참조값이 그대로 넘어가 Callee 안에서 실행되므로 원본 데이터에 영향을 미칩니다.


call by valuecall by reference
값에 의한 호출방식은 인자로 받은 값을 복사하여 처리합니다.참조에 의한 호출방식은 이자로 받은 값의 주소를 참조하여 직접 값에 영향을 미칩니다.
원래 값이 수정되지 않습니다.원래의 값이 수정됩니다.
변수의 복사한 값이 전달됩니다.변수 자체가 전달됩니다.
실제 인수가 다른 메모리 위치에 생성됩니다.실제 인수가 같은 메모리 위치에 생성됩니다.

Java는 Call by value이다

스프링에서 매개변수로 참조 타입의 변수를 넘겨주는 경우, 실제 Heap 메모리에 저장되어 있는 값에 변경이 일어나므로 Call by reference처럼 생각될 수 있습니다.

하지만, Java는 Call by value로만 동작합니다.

이를 설명하기 위해서 Java에서 변수가 메모리에 어떻게 저장되는지에 대한 설명으로 이어가겠습니다.

Java에서 사용되는 변수는 Stack 메모리에 저장이 되었다가 관련 로직 수행이 끝나면 제거되는 방식으로 사용됩니다.

원시타입(int)의 경우, 실제 원시값(10)을 저장합니다.

참조타입(A)은 Heap 영역에 생성된 객체(new A())의 참조값(주소)을 저장합니다.

public class callbyvaluetest {
    public static void main(String[] args) {
        int x = 0; // 1
        
        modify(x); // 2

        System.out.println(x);
    }

    public static void modify(int x){
        x = 10; // 3
    }
}

/** 출력 결과
 * 0
 */

위처럼 원시타입은 변수의 값을 변경한 뒤, 출력을 해봐도, 값이 바뀌지 않습니다.

이를 메모리 구조로 확인해보면, 다음과 같습니다.

main 메서드 내 x라는 변수와 modify 메서드 내 x라는 변수는 다른 변수입니다.
따라서, modify를 통해 값을 변경하여도 실제 main 메서드 내 x 라는 원시타입의 변수에 값이 변하지 않는 것입니다.

public class callbyvaluetest {
    public static void main(String[] args) {
        int x = 0;
        modify(x);
        System.out.println(x);

        A a = new A();
        System.out.println(a.num);
        modifyRef(a);
        System.out.println(a.num);
    }

    public static void modify(int x){
        x = 10;
    }

    public static void modifyRef(A a){
        a.num = 10;
    }
}

class A{
    int num = 0;
}

/** 출력 결과
 * 0 // 변경 전
 * 10 // 변경 후
 */

하지만, Callee의 인자 타입이 참조형인 경우에는 메모리에 다음과 같이 저장됩니다.

즉, Caller에 생성된 A라는 객체의 참조 값이 Callee의 인자값에 복사되어 같은 객체를 참조하게 됩니다.

따라서, modifyRef 메서드에서 a라는 객체의 값을 변경해주면, 실제 메모리 안에 값이 변경되게 됩니다.

이와 같이 원본값이 변경되는 것을 막아주기 위해서는 얉은 복사가 아닌 깊은 복사를 해줘야 합니다.

즉, Callee 인자 a에 new A() 생성자를 추가해줌으로써 새로운 객체를 참조하도록 하는 것입니다.

public class callbyvaluetest {
    public static void main(String[] args) {
        int x = 0;
        modify(x);
        System.out.println(x);

        A a = new A();
        System.out.println(a.num);
        modifyRef(a);
        System.out.println(a.num);
    }

    public static void modify(int x){
        x = 10;
    }

    public static void modifyRef(A a){
        a = new A(); // 추가
        a.num = 10;
    }
}

class A{
    int num = 0;
}

/** 출력 결과
 * 0 // 변경 전
 * 0 // 변경 후
 */

이러한 경우, 실제 원본값에는 영향이 없어지고 다음의 그림처럼 결과가 나옵니다.

따라서, 원본 데이터에는 영향을 미치지 않게 됩니다.

즉, 이를 통해서 알 수 있는건 Java에서는 기본적으로 Call by value에 의해서 복사된 값이 인자로 전달되고, 의도하지 않은 경우에 원본 데이터를 보호하기 위해서는 반드시 깊은 복사(새로운 객체를 참조)해주어 함수를 처리해주면 되겠습니다.

profile
백엔드 서버 엔지니어

0개의 댓글