Java는 call by value일까? call by reference일까?

3 분 소요

업데이트:


Java는 call by value일까? call by reference일까?

  • 결론부터 말하자면 Java는 call by value!
  • 그 이유에 대해 예시를 작성하면서 알아볼 예정이다.


C++에서의 call by value vs call by reference

  • call by value냐 call by reference냐를 설명하기 위한 가장 대표적인 예시는 단연 swap 함수일 것이다.
  • 먼저 call by value의 아주 대표적인 예시이다.

    #include<stdio.h>
      
    void swap(int a, int b) {
      
        int tmp = a;
        a = b;
        b = tmp;
      
    }
      
    int main() {
      
        int a = 10, b = 20;
      
        printf("swap 함수 호출 전... a: %d, b: %d\n", a, b);
      
        swap(a, b);
      
        printf("swap 함수 호출 후... a: %d, b: %d\n", a, b);
      
    }
    
  • 결과는 아래와 같다.

    swap 함수 호출 전... a: 10, b: 20
    swap 함수 호출 후... a: 10, b: 20
    
  • 일반적인 값이 들어있는 변수를 파라미터로 전달했을 때, 파라미터로 전달한 메서드 내에서 값을 바꾸더라도, 원본값은 변하지 않는다.
  • 이유는 함수를 호출할 때 파라미터 값이 복사되어 전달되기 때문이다.
  • 그렇다면, 주소값을 파라미터로 넘겨 swap 함수를 호출하면 어떻게 될까?
  • 다음으로 call by reference의 예시이다.

    #include<stdio.h>
      
    void swap(int* a, int* b) {
      
        int tmp = *a;
        *a = *b;
        *b = tmp;
      
    }
      
    int main() {
      
        int a = 10, b = 20;
      
        printf("swap 함수 호출 전... a: %d, b: %d\n", a, b);
      
        swap(&a, &b);
      
        printf("swap 함수 호출 후... a: %d, b: %d\n", a, b);
      
    }
    
  • 결과는 다음과 같다.
    swap 함수 호출 전... a: 10, b: 20
    swap 함수 호출 후... a: 20, b: 10
    
  • 위와 같이 a,b의 값이 변경되는 것을 확인할 수 있다!
  • 즉, 주소값을 파라미터로 넘겨 함수를 호출하면, 함수를 호출한 쪽의 원본 값을 바꿀 수 있고, 이런 방식으로 함수를 호출하는 방식을 call by reference라고 한다.


그렇다면 Java에서는??

  • 자바의 변수에는 크게 primitive type과 reference type이 있다.
  • 먼저 primitive type을 파라미터로 전달하는 예시를 살펴보자.

    package test;
      
    public class Test {
      
        public static void swap(int a, int b) {
      
            int tmp = a;
            a = b;
            b = tmp;
      
        }
      
        public static void main(String[] args) {
            int a = 10, b = 20;
      
            System.out.printf("swap 함수 호출 전... a: %d, b: %d\n", a, b);
      
            swap(a, b);
      
            System.out.printf("swap 함수 호출 후... a: %d, b: %d\n", a, b);
        }
    }
    
  • 결과
    swap 함수 호출 전... a: 10, b: 20
    swap 함수 호출 후... a: 10, b: 20
    
  • 역시나 예상대로 swap함수를 호출하더라도 원본값이 변하지 않는다.
  • 왜 그럴까? 자바의 메모리 구조를 통해 더 깊게 파고들어보자.

  • swap 함수가 호출되면, main thread내에 swap frame이 새로 생성되어 파라미터로 전달된 a,b에는 값이 복사되어 전달된다.
  • swap 함수가 종료되면, swap frame이 사라지고 swap frame의 변수들도 같이 사라진다.
  • 그렇기 때문에 main frame의 변수에는 아무런 영향을 줄 수 없다.
  • 그렇다면 reference type을 파라미터로 전달해 해당 메서드 내에서 값을 변경하면 어떻게 될까?
  • reference type으로 선언된 참조 변수에는 참조값이 들어가니까, 값이 변하지 않을까?

    package test;
      
    class Player {
      
      private String name;
      
      private int salary;
      
      private int age;
      
      public Player(String name, int salary, int age) {
        this.name = name;
        this.salary = salary;
        this.age = age;
      }
      
      public void setName(String name) {
        this.name = name;
      }
      
      public void setSalary(int salary) {
        this.salary = salary;
      }
      
      public void setAge(int age) {
        this.age = age;
      }
      
      public String getName() {
        return name;
      }
    }
      
    public class Test {
      
      public static void changePlayer(Player player) {
      
        player = new Player("Messi", 1000000000, 35);
      
      }
      
      public static void main(String[] args) {
      
        Player player = new Player("Yang", 1000000, 25);
      
        System.out.println("changePlayer 메서드 호출 전 player는 " + player.getName() + " 입니다.");
      
        changePlayer(player);
      
        System.out.println("changePlayer 메서드 호출 후 player는 " + player.getName() + " 입니다.");
      }
    }
    
  • 결과

    changePlayer 메서드 호출 전 player는 Yang 입니다.
    changePlayer 메서드 호출 후 player는 Yang 입니다.
    
  • 예상과는 달리 player는 변하지 않았다.
  • 왜 바뀌지 않았을까?
  • 다시 메모리 구조를 그려가면서 이해해보자!

  • 위와 같이 changePlayer 메서드가 호출되면 main thread 내에 changePlayer-frame이 생성된다.
  • 그리고 매개변수로 참조값이 복사되어 전달된다.
  • 이후, changePlayer의 파라미터에 새로운 객체 (new Player("Messi", 1000000000, 35);)를 할당하면 위와 같이 그려질 것이다.
  • 다음으로, changePlayer가 종료된 후 main 메서드 내의 player의 참조값이 새롭게 변할까?
  • 변하지 않는다! 왜그럴까?
  • changePlayer가 종료되면 changePlayer-frame은 소멸되고, 마찬가지로 main-frame의 player 변수에 아무런 영향을 주지 못하기 때문이다.


결론

  • Java에서는 메소드의 파라미터로 전달한 객체를 호출된 메소드안에서 변경을 할 수 없다.
    • 누군가는 setter를 이용해 객체를 변경할 수 있지 않느냐? 라고 의문을 제기할 수도 있을 것이다.
    • 하지만, setter를 이용해 객체를 변경하는 것은 해당 객체의 속성을 변경하는 것이지 객체 자체를 변경하는 것은 불가능하다.
  • 그렇기 때문에 Java는 call by value이다.

댓글남기기