상세 컨텐츠

본문 제목

[JAVA] Call by Value , Call by Reference의 차이와 heap & stack 관점에서의 이해

JAVA

by young1403 2022. 12. 1. 19:26

본문

언어마다 함수로 파라미터 변수를 전달할 때 언어마다 변수를 넘겨주는 방법이 다르다. Java는 기본적으로 Call By Value 방식으로 값을 전달한다. (C나 C++처럼 변수의 주소값을 가져오고 넘길 수 있는 방법 자체가 없다.) all by Reference와 Call by Value의 차이점에 대해 알아보고, Stack과 Heap의 관점에서 설명을 하려 한다. 

1. Call by Value(Pass by Value)


[값으로써 전달(Pass by Value)]

Pass By Value(값으로써 전달) 복사된 데이터를 전달하여 구성함으로써, 값을 수정하여도 원본의 데이터에는 영향을 주지 않도록 하는 방식이다.(값을 직접적으로 전달)

아래와 같이 Main class, Exam class가 있고 process1 메서드는 파라미터로 Exam 클래스를 받고, process2 메서드는 파라미터로 int형 타입을 받는다.

class Exam {
    private int count;

    //Exam의 생성자
    public Exam(int count) {
        this.count = count;
    }

    //count에 대한 get 과 set 메서드
    public int getCount() {
        return count;
    }

    public void setCount(int num) {
        this.count = num;
    }
}

public class Main {

    //** process메서드 **//
    public static void process1(Exam exam) {
        System.out.println("print1 : " + exam.getCount());
        exam.setCount(5);   //이때의 exam은 exam count=3 의 heap영역을 가르킴
        System.out.println("print2 : " + exam.getCount());
        Exam exam2 = new Exam(7);   //이때의 exam2는 새로운스택, 새로운 heap 영역을 가르킴
        exam = exam2;
        System.out.println("print3 : " + exam.getCount());   //새로 만들어진 stack, heap 영역
        exam.setCount(9);
        System.out.println("print4 : " + exam.getCount());   //새로 만들어진 stack, heap 영역

    }

    //** 메인 메서드 **//
    public static void main(String[] args) {
        Exam exam = new Exam(3);
        process1(exam);
        System.out.println("print5 : " + exam.getCount());    //기존의 stack, heap 영역

        //===============================================================//

        int value = 11;
        process2(value);
        System.out.println("print2-1 : "+ value);
    }

    public static void process2(int num) {
        num = 17;
        System.out.println("print2-2 : " + num);
    }
}

출력 결과

process1 함수는 Exam 클래스의 count값이 3으로 초기화 되있는걸 5와 7로 변경하고 있다. 이 동작을 잘 살펴보자. 
1. 메인메서드에서 process1을 실행시키면서 파라미터의 참조값(exam.count)을 5로 바꾼다. 

2. count값이 7인 exam2로 exam을 초기화한다. exam의 count값은 7로 바뀌었다.

3. process1 함수 종료 후 exam의 count값은 5이다.

 

process1의 파라미터로 넘어간 exam은 새로운 스택영역에 있지만 메인 메서드의 exam과 같은 heap영역을 공유하고 있다. 따라서 process1 함수의 exam.count 값을 바꾸면 메인 메서드의 exam의 heap영역을 가리키고 있는 것이기 때문에 기존의 값이 변경된다.

하지만 process1메서드의 내부에서 생성된 exam2는 새로운 heap영역을 갖고 있다.

그렇기 때문에 exam을 새로 만들어진 exam2로 초기화하면 exam이 가리키는 heap영역은 exam2가 만든 새로운 heap영역이기 때문에 메인 메서드에 있는 기존의 exam.count의 값은 변하지 않는다. (print3,4는 exam2로 만들어진 새로운 heap 영역)

 

process1의 과정이 어렵다면 우선 process2의 과정을 보고 이해해보는걸 추천한다.

Call by Value

 

2. Call by Reference(Pass by Reference)


[참조로써 전달 (Pass by Reference)]

 

Call by reference는 메소드를 호출할 때 넘겨주고 싶은 변수(인자)를 지정하면, 메소드의 매개변수가 지정한 변수의 레퍼런스로 초기화되는 것이다.

(java에서는 객체의 인스턴스를 넘기면 기존 인스턴스의 값에 변화가 일어난다. Call By Reference로 착각할 수 있지만,
참조주소의 '값'을 넘겨서 참조 영역을 공유하는 형태이기에 Call By Value이다.)

아래의 C++코드를 통해 Pass by Reference에 대해 알아보자. 

 

#include <iostream>

using namespace std;

void process(int& value) {
    cout << "Value passed into function: " << value << endl;
    value = 10;
    cout << "Value before leaving function: " << value << endl;
}

int main() {
    int someValue = 7;
    cout << "Value before function call: " << someValue << endl;
    process(someValue);
    cout << "Value after function call: " << someValue << endl;

    return 0;
}
Value before function call: 7                                                                                                                                                    
Value passed into function: 7                                                                                                                                                    
Value before leaving function: 10                                                                                                                                                
Value after function call: 10

 

main 메서드에서 7로 선언된 value값이 있다. process메서드를 통해 7로 넘어온 int 값을 10으로 초기화한다.

다시 돌아와 process메서드가 끝난 후의 value값을 출력해보면 process 함수 안에서의 동작이 함수 밖의 값에까지 영향을 미침을 알 수가 있다.( Pass by Value에서 설명 예시로 든 process2 메서드 동작과 비교해 보면 좋을 것 같다.)

 

메인 메서드에서 선언한 someValue와 process메서드로 넘어간 value는 서로 다른 스택 영역에 있다. 하지만 process내부에서 someValue의 참조 영역을 pointer로 가리키고 있기 때문에(변수의 레퍼런스 자체를 넘겨주는 것)  value의 변화가 someValue에 영향을 계속해서 미치게 된다.

 

Call by Reference

 

 

3. 정리


Pass by Value는 복사된 데이터를 전달하여 구성하는 방식이기 때문에 원본의 데이터는 수정되지 않는다.

Pass by Reference는 주소 값을 전달하기 때문에 값을 수정하면 원본의 데이터가 계속해서 수정된다.

 

취향 차이겠지만 두 가지 중 어느 것을 더 선호하냐라고 누군가 묻는다면 Pass by Value를 선호한다고 대답할 것이다. 왜냐하면 함수 내에서는 원본의 값에 접근할 수가 없고, 원본의 값을 변경할 때에 명시적으로 표시해줘야 (setter메서드나 java에서 this. 와 같은) 하기 때문에 project 내에서 side effect에 대한 걱정을 한시름 덜 수 있다고 생각되서이다.

 

 

(해당 글에서 부족한 점, 수정 및 보완할 것이나 궁금한 점 있으면 댓글 남겨주시면 감사하겠습니다.)

참고 블로그와 글 링크
https://dzone.com/articles/pass-by-value-vs-reference-in-java
https://mangkyu.tistory.com/107

 

'JAVA' 카테고리의 다른 글

[JAVA] Optional : orElse, orElseThrow, orElseGet  (0) 2023.04.20
추상 클래스와 인터페이스의 차이  (0) 2023.04.20
인스턴스 멤버와 정적멤버  (0) 2022.05.12

관련글 더보기

댓글 영역