개발쿠키

[JAVA]Call By Reference, Call By Value 본문

개발/java

[JAVA]Call By Reference, Call By Value

쿠키와개발 2023. 7. 14. 18:28

프로그래밍에서 메소드 매개변수 호출 방식에는 call by reference와 call by value 두가지 방식이 있다. 

 

아마 많은 개발자 분들은 취준 시절에 면접 단골 질문이라고 많이 공부들 하셨을 것이다. 물론 나도 해당 내용에 대하여 찾아보았지만 단순히 대답을 위해서 이해는 제대로 못하고 암기식으로 외웠던 기억이 있다. 

 

하지만 이제는 말할 수 있다!...그럼 본론으로 들어가기 전에 간단히 두가지 방식이 무엇을 의미하는지 알아보자.

  • call by reference - 직역을 하자면 참조에 의한 호출이다. 
  • call by value - 에 의한 호출.

저렇게 적어놓으면 뭔가 더 없나 하겠지만 정말 저게 전부다.

 

우선 자바에서 저 두개를 이해하기 전에 JVM을 알고 있다면 훠어얼씬 이해하기 쉽다. 만약 자세하게 모른다면 아래 두개만 우선 기억하자

  • 자바에서는 다른 메소드를 호출하게 될 경우 스택이라는 영역 내에 프레임이라는 메소드의 상태 정보를 가지고 있는 하나의 단위가 들어간다. 
  • 자바에서 원시 타입의 변수는 스택 프레임 안에서 값과 함께 생성된다. 참조 타입의 변수는 실제 객체의 값은 Heap 영역에 생성되고 해당 변수는 Heap 영역에 생성된 객체의 참조값을 가지고 있다. 

1. Call By Value

Call by value는 쉽게 말해 값을 넘긴다라고 보면 된다. 아래 코드와 같이 말이다.

public static void main(String[] args) {
    int a = 4;
    int b = 7;

    change(a, b);
}

public static void change(int c, int d) {
    c = 20;
    d = 30;
}

그렇다면 저렇게 넘긴 a와 b의 값이 과연 바뀔까? 정답은 아니다 왜 아닌지는 간단하게 그림으로 그리면서 살펴보자.

우선 위에서 말했듯이 메소드를 호출할 경우 스택에는 프레임이라는 것이 들어간다. 코드를 예시로 보면 최초로 main 메소드를 호출하면서 스택에 main 프레임이 들어가게 된다. 

 

main 프레임이 들어간 후 a , b에 각각 4,7을 할당한 후 change 메소드를 호출하면서 매개변수인 c와 d에 main에서 받은 4,7을 똑같이 할당한다. 절대로 main에서 생성된 a,b를 넘긴게 아니다 a와b가 가진 만 넘긴것이다. 

매개변수들을 main으로 부터 받은 값을 할당 한 후 코드에서 각각 20,30을 다시 할당하고 해당 메소드는 종료되면서 스택 프레임에서 pop 된다. 

그림으로만 봐도 main을 전혀 건드리지 않고 내부에서만 값을 변경한 후 종료되는걸 볼 수 있다. 이런식으로 값을 직접적으로 넘기는 방식을 call by value라고하며 값을 직접 넘기는 방식이기 때문에 '값에 의한 호출'이라고 한다. 참고로 자바에서 원시타입은 call by value로 동작한다. 원시 타입의 변수들을 다른 메소드로 넘길때 값만 넘긴다는 뜻이다. 


2. Call By Reference

call by value는 그래도 쉽다...왜냐 값만 넘기고 하니까 복잡한게 딱히 없다. 하지만 call by reference는 좀 복잡하다.

위에서 말했듯이 참조 타입의 변수들은 실제 값들은 Heap 영역에 생성되고 스택 변수에 할당되는 값은 참조값이라고 했다. 그리고 이 참조값을 메소드에게 넘기는 것을 call by reference라고 한다. 아래는 예제 코드이다

class Human {
    private int age;

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

    public void setAge(int age) {
        this.age = age;
    }

    public int getAge() {
        return this.age;
    }
}

public class Main {
    public static void main(String[] args) {

        Human h1 = new Human(10);
        System.out.println(h1.getAge()); //10

        humanChange(h1);

        System.out.println(h1.getAge()); //30
    }

    public static void humanChange(Human h) {
        h.setAge(30);
    }
}

아래 그림을 통해 해당 코드를 수행하게 되면 어떻게 메모리에 할당되는지 보자

  • main에서 h1 객체를 생성하면서 실제 값은 Heap에 들어가고 h1은 해당 객체의 주소값을 들고 있다.
  • humanChange 메소드 호출시에 h1의 주소값을 넘겨주고 h는 넘겨받은 객체의 주소값을 들고 있다.
  • 해당 메소드에서 h.setAge(30)을 실행하면서 해당 객체의 age 값을 변경한다. 
  • 메소드가 종료되고 값 Heap 영역에 존재하는 Human 객체의 age 값이 변경된다. 

이러한 순서로 동작하면서 객체의 실제 값이 바뀌어 결과값은 10 30이 나오게 된다. 

이게 바로 call by reference다 라고 하면 아마 많은 자바 개발자 분들의 관심을 살 수 있을것이다. 실제로 그림을 저렇게 그려놓고 본다면 정말 call by reference라고 믿게된다. 

 

그럼 저게 왜 call by value인지 빠르게 알아보자.

public static void main(String[] args) {

    Human h1 = new Human(10);
    Human h2 = new Human(20);

    System.out.println(h1.hashCode());
    System.out.println(h2.hashCode());

    changeHuman(h1, h2);

    System.out.println(h1.hashCode());
    System.out.println(h2.hashCode());
}

public static void changeHuman(Human n1, Human n2) {
    System.out.println("=======swap======");
    System.out.println(n1);
    n1 = n2;
    System.out.println(n1);
    System.out.println(n2);
    System.out.println(n1.getAge());
    System.out.println(n1.hashCode());
    System.out.println(n2.hashCode());
    System.out.println("=======swap======");
}
====결과값==== 
2003749087
1324119927
=======swap======
javastudy.Human@776ec8df
javastudy.Human@4eec7777
javastudy.Human@4eec7777
20
1324119927
1324119927
=======swap======
2003749087
1324119927

코드를 간략히 설명하면 changeHuman()에서 객체 두개를 받아서 n1에 n2를 할당하는 코드이다. 과연 이렇게 했을 때 main에서 선언한 객체가 바뀌었을까를 확인해보면 전혀 바뀌지 않았다. 

 

아래 그림으로 보면 

이렇게 되어 있는 상태에서 n1의 주소값을 바꿔서 n2를 참조하게 만들 경우 h1의 값도 바뀌냐는 것이 중요하다. 

여기서 바뀌지 않는 이유는 간단하다. 단순하게 생각해서 n1은 humanChange의 지역 변수이기 때문에 해당 메소드 내에서만 바뀌었다고 볼 수 있다. 이해가 잘 안되면 위의 그림을 보면 도움이 된다. 해당 메소드의 모든 로직이 끝나기 까지 humanChange  메소드에서 main을 건드리거나 한 것이 없다 그저 자신의 영역에서 전달받은 주소값을 바꾸고 한게 끝이다. 마치 call by value에서 동작하는것과 비슷하게 돌아간다. 

 

이러한 이유가 call by reference라고 보기 어려운 이유이며 자바에서는 call by value로 동작한다고 하는 이유이다. 그렇다면 setAge는 가능했던 이유는 무엇인지 생각해보면 위에서 언급했듯이 메소드 내에서 메소드를 호출하면 프레임이 쌓이면서 새로운 메소드 영역이 생긴다. 여기서 setAge는 Heap 영역에 존재하는 Human의 메소드이기 때문에 해당 메소드에서 직접 자신의 값을 바꾸기 때문에 가능한 것이다. 

다시 말해 changeHuman이 바꾼게 아니라는 의미이다. 

'개발 > java' 카테고리의 다른 글

[JAVA]객체지향과 객체지향 프로그래밍(OOP)  (0) 2023.07.31
[JAVA]클래스, 객체, 인스턴스  (0) 2023.07.20
[JAVA]JVM  (0) 2023.07.06
[JAVA]String  (0) 2023.07.04
[JAVA]JVM, JRE, JDK  (0) 2023.07.03