코드 그라데이션

[Java] Call by Value, Call by Reference 본문

Java, SpringBoot 추가 공부

[Java] Call by Value, Call by Reference

완벽한 장면 2023. 4. 2. 12:52

Call by Value

예시

package Sample

// 어떻게 보면 자바에서는 그냥 통칭하여 call by value라고 할 수도 있음.
  /*
  예시, swap이란 함수를 써서 값을 바꾸기만 했으면 좋겠다.
  */
  
public class CallbyExample {

  static void swap(int a, int b) {
    int temp = a;
    a = b;
    b = temp;

    System.out.println(a); // 20 출력
    System.out.println(b); // 10 출력
  }


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

    swap(a, b);

    System.out.println(a); // 10 출력
    System.out.println(b); // 20 출력

  }
}

call by value

일단 지금 변수 a와 b를 main에서 만들었다.
그럼 main에는 이 함수만을 위한 공간이 따로 있다고 생각하면 됨.
여기에 a라는 공간이 만들어지고 여기에 값이 10이 들어가는 상황
b도 만들어지고 여기에 값이 20이 들어가는 상황이다.

또 swap을 불렀으니까, 함수 스택이 만들어지고,
파라미터로 받은 int a와 int b를 위한 공간이 만들어지고
그 공간에 10과 20이 들어간다.

그런데 이 공간 a와 b는 서로 완전히 독립적이라서
main과 swap 스택은 서로 아무 영향을 주지 않는다.

그리고 그 다음에 어떤 일이 일어나냐면
swap에 a와 b를 넘긴다는 건
원본이 넘어간다는 것이 아니라, 가지고 있는 값이 "복사된 값이 넘어간다"는 뜻이다.
그러니까 핵심은 파라미터로 값을 가져올 때, 값만 가져오는 거지,
main에 선언된 원본을 가져오는 건 아니라는 뜻.

즉 a를 가져오고 b를 가져오는 것이 아니라
a에 담겨있는 10만 가져오고 b에 담겨있는 20만 가져와서 파라미터로 넣는다!

따지고 보면 main에서의 a, b와
swap 에서의 a, b 가 공존하는 모양새이다.

 

그래서 swap은 잘 되고, a와 b의 값은 서로 바뀌고

출력을 찍어보면 바뀐 채로 잘 나온다.

 

그런데, 중괄호가 닫히면 함수는 끝난다!

그럼 swap이란 공간이 날아간다.
main만 다시 남게 되는 건데,

main에서의 a, b는 swap에서의 a, b와 전혀 관련이 없기 때문에(독립적)
main에서 a와 b의 값을 출력해보면, 원래 선언한 그대로의 값인
a = 10,
b = 20 이 출력된다.

 

------------------------------------------------

Call by Reference

예시

public class CallByReferenceSample {

  static void changeName(Person person) {
    person.name = "B";
  
    System.out.println(person.name); //B 출력
    
  }

  public static void main(String[] args) {
    Person person = new Person();
    person.name = "A"; // 객체 생성 후 name을 변경

    changeName(person);

    System.out.println(person.name); // **B 출력
  }
}

 

먼저 동일하게 main을 위한 공간과 changeName 을 위한 공간이 생긴다.
그리고 우리는 main에 대한 참조변수를 생성함. (main에 person 공간이 생성)
참조변수인 person 공간 안에는 무엇이 들어가냐? 바로 위치, 주솟값!(ex. 100번지)
이 위치는 person 객체의 위치!

person 객체의 위치는 new를 통해서 만들었기 때문에, 이런 동적인 메모리는
Heap이라는 공간에 저장됨. Heap에 person이 만들어진다는 소리다.
그리고 이 사람이 있는 위치가 100번지라는 말이다.

changeName에 매개변수로 person을 갖다주면 chqngeName에도 person의 공간이 하나 만들어진다.
그럼 여기서 만들어진 person과 main에서 만들어진 person은 다른 게 확실하다.(완전 독립적, 남남)

그런데, 이것 역시 call by value라고 인지를 하면,
call by value 에서는 값이 복사가 되었지.
따라서 changeName의 공간의 person에도 100번지(=값)가 똑같이 복사가 된다.(값이 복사되었다고 생각하세요)

그럼 main과 changeName()은 값이 다 들어와 있고, 별개의 변수도 있는 상태가 됨.
그런데 여기서 person.name을 'B'로 바꾼다는 게 뭐냐면,
어차피 얘(changeName에 있는 person)가 참조변수잖아. 이 person을 지지고 볶고 하는 게 아니라,
Heap에 있는 위치(100번지)를 찾아가는 것이다.
그리고 얘(heap에 있는 것)의 이름을 B로 바꾸는 것.
이게 changeName()에서 일어난 일이다.

그리고 이 메서드가 끝나면 얘가 없어지고 person도 날아간다.

이제 남은 main을 보면,
여기서도 출력할 때, person.name을 했죠.
따라서 main에서도 Heap에 있는 위치(100번지) 를 똑같이 찾아간다.
그럼 Heap에는 이미 이름이 B로 변경되어 있는 상태이다(아까 했으니까)

그래서 main에서의 변화도 밖에서도 보이는 것이다.

 

그래서 사실상자바에서는 모두 call by Value라고 봐도 될 것 같다.

 

다만, 그 복사되는 것이 primitive 타입이냐, 참조형이냐에 따라서 달라짐.

 

전자는 값이 복사되는 것이고, 후자는 위치(주소)가 복사되는 것.
주소면, 주소가 복사가 되는데 이것은 '원본'의 주소이기 때문에
원본을 변화시킬 수 있다.

 

그래서 함수에서의 변화가 바깥에서도 보이는 것.
값과 주소의 차이만 인지하고 있으면
사실상 두 개의 매커니즘은 동일한 것으로 인지해도 무방하다.

참고할 만한 상황이 뭐냐면
primitive 타입에서는 값을 전달하는 방법이 return밖에 없으니까
무조건 return을 했어야 했다.
아래처럼...
class Person02 {
    String name;

}

public class CallByReferenceSampe02 {

  static Person changeName(Person person) { // 이렇게!!!!
    person.name = "B";

    return person;

  }

  public static void main(String[] args) {
    Person person = new Person();
    person.name = "A"; // 객체 생성 후 name을 변경

    changeName(person);

    System.out.println(person.name);
  }
}

 

그런데 객체 세상에서는 changeName(Person person) 내부에서의 변화가
return을 쓰지 않더라도, 내가 Person person만 바꿔도 외부로 영향이 가지.

  즉,

  static void changeName(Person person) {
    person.name = "B";
  }

이렇게만 해도 된다는 이야기.

즉, 메서드의 인자가 primitive 타입이 아니라 클래스(참조자료형) 이면
내가 얘를 리턴해서 쓰지 않고도 값과 상호작용이 가능하다는 이야기.

주소값을 참조해서 원본에 손을 댈 수 있는 상황이 되니까.
return을 넘기지 않아도 인자를 넘김으로써 이 값 자체가 수정이 가능하다는 이야기.

728x90
Comments