본문 바로가기

Language/Java

Call by Value와 Call by Reference의 차이

Call by Value

  • 메서드를 호출할 때 값을 넘겨주기 때문에 Pass by Value 라고도 함
  • 메서드를 호출하는 호출자(Caller)의 변수호출 당하는 수신자(Callee)의 파라미터복사된 서로 다른 변수
  • 값만을 전달하기 때문에 수신자의 파라미터를 수정해도 호출자의 변수에는 영향 X

Call by Reference

  • Call by Reference는 참조(주소)를 직접 전달하며 Pass by Reference 라고도 함
  • 참조를 직접 넘기기 때문에 호출자의 변수수신자의 파라미터완전히 동일한 변수
  • 메서드 내에서 파라미터를 수정하면 그대로 원본 변수에도 반영

JVM 메모리에 변수가 저장되는 위치

  • Java의 Call by Value에 대해 이해하기 위해서는 변수 생성 시 메모리에 어떻게 저장 되는 지 알아야 함
  • Java에서 변수를 선언하게 되면 Stack 영역에 할당
  • 원시 타입(Primitive Type)Stack 영역에 변수와 함께 저장
  • 참조 타입(Reference Type)객체는 Heap 영역에 저장되고 Stack 영역의 변수가 객체의 주소를 가짐


원시 타입(Primitive Type) 전달

  • 원시 타입은 Stack 영역에 위치
  • 메서드 호출 시 전달 받은 파라미터들도 원시 타입이라면 Stack 영역에 생성
  • 다음 코드에서 main의 변수 a, bmodify의 파라미터 a, b의 이름은 동일하지만 다른 변수
  • modify(a, b)를 호출하는 순간 Stack 영역에는 main의 a, b의 값을 복사하여 새로운 변수 a, b가 생성되어 총 4개의 변수가 존재
public class Main {
    public static void main(String[] args) {
        int a = 1;
        int b = 2;

        modify(a, b);

        System.out.println(a); // 1
        System.out.println(b); // 2
    }

    private static void modify(int a, int b) {
        a = 5;
        b = 10;
    }
}

  • modify의 파라미터 a, b의 값을 변경하더라도 원본인 main의 a, b 값에는 영향을 미치지 않음
  • 즉, 원시 타입의 전달은 Call by Value로 동작

참조 타입(Reference Type)의 전달

  • 참조 타입은 원시 타입과 조금 다름
  • 변수 자체는 Stack 영역에 생성 되지만 실제 객체는 Heap 영역에 위치하고, Stack에 있는 변수가 Heap에 있는 객체를 바라보는 형태
class User {
    public int age;

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

public class Main {
    public static void main(String[] args) {
        User a = new User(10);
        User b = new User(20);

        // Before
        System.out.println(a.age); // 10
        System.out.println(b.age); // 20

        modify(a, b);

        // After
        System.out.println(a.age); // 11
        System.out.println(b.age); // 20
    }

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

        // 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()에서 생긴 변수들이 주소값을 보고 객체를 같이 참조하는 것
main() 메서드 a, b 선언
  • a, b 변수는 Stack 영역에 생성
  • a, b 변수는 Heap 영역에 있는 객체를 바라봄

 

modify(a, b) 호출
  • Stack 영역에 a, b 변수 생성
  • 넘겨받은 주솟값(Heap 객체)을 바라봄

 

modify(a, b) 수행 직후 메모리 상태
  • modify() 영역에 존재하는 a, b 변수는 각각 100번과 200번 주소의 User를 바라보고 있었지만, b = new User(30)으로 새로운 객체를 할당하여 b는 300번 주소의 User 객체를 바라봄
  • 따라서, b.age++는 300번 주소의 User age 을 증가

 

main() 메서드가 끝난 뒤 최종 메모리 상태
  • modify(a, b) 메소드 종료 후 Stack 영역에 할당된 modify()의 a, b 변수들은 사라짐
  • 최종적으로는 다음과 같은 상태가 되어 300번 주소의 User는 어떤 곳에서도 참조가 되지 않기 때문에 나중에 Garbage Collector에 의해 제거될 예정


결론

  • 주소 값을 넘긴다 = Call by Reference라고 생각할 수 있지만, Call by Reference는 참조 자체를 넘기기 때문새로운 객체를 할당하게 되면 원본 변수도 영향을 받음
  • 가장 큰 핵심은 호출자 변수와 파라미터는 Stack 영역 내에서 각각 존재하는 다른 변수
  • 따라서, 원본의 참조값이 변경되지 않는다는 점에서 자바는 Call by Value라고 불림

Call By Reference가 쓰이는 경우
  1. 함수 내에서 값을 수정해야할 때
  2. 메모리를 효율적으로 사용해야할 때
    복사가 아닌 참조를 전달하여 메모리의 사용을 줄일 수 있기 때문
  3. 성능을 최적화할 때
    → 복사에 시간이 많이 걸리는 큰 객체나 데이터를 처리할 때 Call by Reference 를 사용함으로써 메서드 호출의 오버헤드를 낮출 수 있음
  4. 여러 개의 값을 반환받고 싶을 때
    → 하나의 메서드는 1개의 결과만을 반환하는데, 여러 개의 값을 반환받고 싶을 때 여러 개 참조를 인자로 넘겨 값을 수정하게 되면 마치 여러 개의 결과를 반환 받은 것과 같은 효과를 낼 수 있음