Call-By-Value, Call-By-Reference란?
CallByValue: 메소드 호출시 변수의 ‘값’을 전달하는 방식
(메소드 호출시에, 피호출자 메서드의 매개변수에 (본래) 호출자 변수의 복제값을 전달 하는방식)
CallByReference: 메소드 호출시 변수의 주소값을 전달하는 방식
(피호출자 메서드의 매개변수에 (본래)호출자 변수의 주소(고유 식별자)를 메서드로 전달하는 방식)
JVM 메모리에 변수가 저장되는 위치
먼저 Java에서 변수 생성 시 메모리에 어떤 식으로 저장되는지 알아보자.
Java에서 변수를 선언하면 Stack 영역에 할당된다.
여기서 변수가 원시 타입(Primitive Type)이라면 Stack 영역에 변수와 함께 저장된다.
반면에 참조 타입(Reference Type)이라면 객체는 Heap 영역에 저장되고, Stack 영역에는 해당 객체의 주소값 갖는 변수가 저장된다.
이제 다시 본론으로 넘와서 각 타입별로 파라미터를 넘겨줄 때 어떤 식으로 동작하는지 알아보자.
메소드에 원시타입 변수 전달
@Test
@DisplayName("primitive type 변수로 메소드 호출하기")
void primitiveValueTest() {
int x = 10;
int y = 20;
System.out.println("Before Modification");
System.out.println("###x: " + x);
System.out.println("###y: " + y);
modify(x, y);
System.out.println("After Modification");
System.out.println("###x: " + x);
System.out.println("###y: " + y);
}
public void modify(int a, int b) {
a = 11;
b = 22;
}
Before Modification
###x: 10
###y: 20
After Modification
###x: 10
###y: 20
그림을 보면 modify() 메소드 호출시 본래 변수의 '값'을 파라미터 인자로 복사해서 전달하는 것을 알 수 있다.
=> 원시 타입의 변수 전달은 값을 전달하는 Call by Value로 동작!
메소드에 레퍼런스 타입 변수 전달
@Test
@DisplayName("reference type변수로 메소드 호출하기")
void referenceValueTest() {
Foo x = new Foo(10);
Foo y = new Foo(20);
System.out.println("Before Modification");
System.out.println("###x: " + x.num);
System.out.println("###y: " + y.num);
modify(x, y);
System.out.println("After Modification");
System.out.println("###x: " + x.num);
System.out.println("###y: " + y.num);
}
private class Foo {
public int num;
public Foo(int num) {
this.num = num;
}
}
public void modify(Foo a, Foo b) {
a.num = 11;
b.num = 22;
}
Before Modification
###x: 10
###y: 20
After Modification
###x: 11
###y: 22
메소드 호출전
메소드 호출시점
메소드 호출시 레퍼런스 변수의 값, 즉 본래 오브젝트의 주소값을 복사해서 전달한다.
메소드 내부값 변경
최종 결과
본래 오브젝트의 값이 변경되긴 했다. 하지만 우리가 여기서 고려할 점은 x, y라는 레퍼런스 변수의 주소값을 전달하지 않고 x, y가 가지고 있는 '값'이다. 즉, 오브젝트의 실제 주소값을 가지고 있는 변수 x, y의 값을 전달한 것이다.
따라서 자바에서의 레퍼런스 변수타입 역시 Call by Value로 동작함을 알 수 있다.
그렇다면 도대체 CallByReference란 무엇일까? C++에서 예제를 한번 가져와 봤다.
CallByReference 예제
#include <iostream>
using namespace std;
void swap(int *x, int *y){
int temp = *x;
*x = *y;
*y = temp;
}
int main(void){
int a = 1;
int b = 2;
swap(&a, &b);
cout <<"a : "<< a << endl;
cout <<"b : " <<b << endl;
system("pause");
return 0;
}
java에서와는 다르게 변수 a, b의 '실제 값'이 아닌 본래 변수의 주소값을 메소드 파라미터로 전달하는 것을 볼 수 있다.
이것이 CallByReference인듯하다!
결론
java => CallByValue
중요한 것은 변수가 변하는지의 여부가 아니다. 함수 호출로 새롭게 스택에 쌓이는 수신자 파라미터가 원본 변수의 복사된 ’값’이냐 아니면,
스택에 저장된변수를 가르키는 주소값이냐이다. 위의 내용을 바탕으로 보았을 때, 자바는 CallByValue이다.
왜냐하면 수신자 파라미터가 레퍼런스 변수의 주소값을 전달 받지 않고, 실제 오브젝트를 가리키는 레퍼런스의 ‘값’을 복사해서 전달하기 때문이다 .뭐 공부하고나서 보니,C++과 다르게 변수의 주소 제어 권한이 없는 자바입장에서는 당연한 말이다..
*그렇다면 번외로 왜 자바에서는 주소제어 권한을 개발자들에게 위임하지 않았을까?
=> 나의 주관적인 생각은 이렇다. 주소 제어권한을 사용하는 이유와 목적은 대게 메모리 관리 측면이 강할 것이다. 하지만 java에서는 이러한 메모리 관리를 jvm의 gc를 통해 자동으로 관리해 준다! 따라서 자바의 초기 개발자는 gc라는 자바의 동작 방식과 개발자의 개별적인 메모리관리가 충돌하지 않게하려고 한 의도처럼 보인다. java를 보다 안정감 있게 작동시키기 위해서 주소 제어 권한을 위임하지 않은 것으로 고려된다.
'java' 카테고리의 다른 글
Checked Exception vs UncheckedException (1) | 2022.12.19 |
---|---|
정규표현식 (0) | 2022.12.19 |
Enum (0) | 2022.12.19 |
Static (0) | 2022.12.18 |
Junit5 (0) | 2022.12.18 |