[Java] 콜바이 레퍼런스, 콜바이 벨류

나른한 개발자·2025년 12월 30일

f-lab

목록 보기
6/46

Call By Reference / Call By Value

메서드에 파라미터를 넘기는 방식에는 두 가지가 있다.

  • Call By reference
  • Call By Value

Call By reference

  • 메서드를 호출할 때 주소 값을 넘겨주는 방법이다.
  • pass by reference라고도 한다.
  • 참조를 직접 넘기기 때문에 메서드를 호출하는 호출자의 변수와 호출 당하는 수신자의 파라미터는 완전히 동일한 변수다.

Call By Value

  • 메서드를 호출할때 값을 복사하여 값을 넘겨주는 방법이다.
  • pass by value 라고도한다.
  • 호출자의 변수와 수신자의 파라미터는 복사된 서로 다른 변수다.
  • 복사된 값을 전달하기 때문에 메서드 내에서 파라미터를 수정해도 호출자 변수에는 아무런 영향이 없다.

자바에서의 파라미터 전달 방법

자바에서는 파라미터 전달을 어떤 방식으로 할까?
call by reference 라고 오해하기 쉽지만, 사실 call by value로만 동작한다.

이것을 이해하기 위해 변수 생성시 JVM 메모리의 어떻게 저장되는지 살펴보도록 하자.

자바에서 변수를 선언하면 stack영역에 할당이 된다.
원시 타입은 값과 변수가 stack 영역에 할당이 되고,
참조 타입은 변수는 stack 영역에, 실제 객체는 영역에 저장이 된다.

원시 타입과, 참조 타입을 파라미터로 전달했을 때 어떻게 전달되는지 살펴보자.

원시 타입 전달

public class PrimitiveTypeTest {

    @Test
    @DisplayName("Primitive Type 은 Stack 메모리에 저장되어서 변경해도 원본 변수에 영향이 없다")
    void test() {
        int a = 1;
        int b = 2;

        // Before
        assertEquals(a, 1);
        assertEquals(b, 2);

        modify(a, b);

        // After: modify(a, b) 호출 후에도 값이 변하지 않음
        assertEquals(a, 1);
        assertEquals(b, 2);
    }

    private void modify(int a, int b) {
        // 여기 있는 파라미터 a, b 는 이름만 같을 뿐 test() 에 있는 a, b 와 다른 변수
        a = 5;
        b = 10;
    }
}

원시 타입은 Stack 영역에 위치한다. 메서드 호출 시 넘겨받는 파라미터들도 원시 타입이라면 Stack 영역에 생성된다.

위 코드에서 test() 의 변수 a, b 와 modify(a, b) 로 전달받은 파라미터 a, b 의 이름과 값은 같다.

하지만 다른 변수이다.

modify(a, b) 를 호출하는 순간 Stack 영역에 새로운 변수 a, b 가 새로 생성되어 총 4 개의 변수가 존재한다.

Stack 내부에 test() 와 modify() 라는 영역이 나뉘어져 있고 거기에 동일한 이름을 가진 변수 a, b 가 존재합니다.

그래서 modify() 영역의 값을 바꿔도 test() 영역의 변수는 변화가 없습니다.

원시 타입의 전달은 값만 전달하는 Call by Value 로 동작합니다.

참조 타입 전달

class User {
    public int age;

    public User(int age) {
        this.age = age;
    }
}

public class ReferenceTypeTest {

    @Test
    @DisplayName("Reference Type 은 주소값을 넘겨 받아서 같은 객체를 바라본다" +
                 "그래서 변경하면 원본 변수에도 영향이 있다")
    void test() {
        User a = new User(10);
        User b = new User(20);

        // Before
        assertEquals(a.age, 10);
        assertEquals(b.age, 20);

        modify(a, b);

        // After
        assertEquals(a.age, 11);
        assertEquals(b.age, 20);
    }

    private void modify(User a, User b) {
        // a, b 와 이름이 같고 같은 객체를 바라본다.
        // 하지만 test 에 있는 변수와 확실히 다른 변수다.

        // modify 의 a 와 test 의 a 는 같은 객체를 바라봐서 영향이 있음
        a.age++;

        // b 에 새로운 객체를 할당하면 가리키는 객체가 달라지고 원본에는 영향 없음
        b = new User(30);
        b.age++;
    }
}

원시 타입 코드와 마찬가지로 동일한 변수 a, b 가 존재한다.

여기서 modify(a, b) 를 호출한 후에 a.age 의 값이 변경되었기 때문에 Call by Reference 로 파라미터를 넘겨주었다고 착각하기 쉽다.

하지만 Reference 자체를 전달하는 게 아니라 주소값만 전달해주고 modify() 에서 생긴 변수들이 주소값을 보고 객체를 같이 참조하고 있는 것이다.

단계별 그림으로 확인해보면 다음과 같다.

처음 변수 선언 시 메모리 상태


원시 타입과는 다르게 변수만 Stack 영역에 생성되고 실제 객체는 Heap 영역에 생성된다.

각 변수는 Heap 영역에 있는 객체를 바라보고 있다.

modify(a, b) 호출 시점의 메모리 상태


넘겨받은 파라미터는 Stack 영역에 생성되고 넘겨받은 주소값을 똑같이 바라본다.

modify(a, b) 수행 직후 메모리 상태


test() 영역과 modify() 영역에 존재하는 a 라는 변수들은 같은 객체인 User01 을 바라보고 있기 때문에 객체를 공유한다.

b 라는 변수는 서로 같은 객체인 User02 를 바라보고 있었지만 modify(a, b) 내부에서 새로운 객체를 생성해서 할당했기 때문에 User03 이라는 객체를 바라본다.

그래서 User03 의 age 값을 변경해도 test() 에 있는 b 에는 아무런 변화가 없다.

test() 끝난 후 최종 메모리 상태


modify(a, b) 메서드를 빠져나오면 Stack 영역에 할당된 변수들은 사라진다.

최종적으로 위와 같은 상태가 되며 User03 은 어떤 곳에서도 참조되고 있지 않기 때문에 나중에 Garbage Collector 에 의해 제거될 것이다.

이렇게 주소 값이 전달되는 것이라서 call by reference라고 오해하기 쉽지만, 자바는 언제나 call by value 방식으로 동작한다. 만약 자바가 call by reference 였다면, 파라미터에 참조 자체를 넘겨주는 것이기 때문에 새로운 객체를 할당하면 원본 변수도 영향을 받게될 것이다.

참고링크

메서드에 파라미터를 넘겨주는 방식에는 콜바이 레퍼런스와 콜바이 벨류가 있습니다. 콜바이 레퍼런스는 파라미터에 참조값을 전달해주는 것입니다. 따라서 호출자의 변수와 수신자의 파라미터는 동일한 변수입니다. 반면 콜바이 벨류는 파라미터에 값을 복사하여 전달해주기 때문에 호출자의 변수와 수신자의 파라미터는 전혀 다른 변수가 되고, 메서드 내에서 매개변수를 수정해도 원본 변수에는 영향이 없습니다. 자바에서는 콜바이 벨류 방식으로 동작합니다. 파라미터로 변수를 넘겨줄때 값을 복사하여 stack 영역에 새로이 할당하고 메서드가 끝나면 사라집니다. 매개 변수가 참조타입인 경우는 변수가 가진 값이 객체의 참조값이기 때문에 call by reference라고 오해하기 쉽지만, 자바는 항상 call by value로만 동작합니다.

profile
Start fast to fail fast

0개의 댓글