결론부터 말하자면 Call By Value 다. Call By Reference처럼 동작하는 것 같기도한데? 하며 헷갈릴 수 있는 부분을 정확히 그림을 통해 어떤 차이점이 있는지 비교 해보고자 한다.
Call By Value란 메소드의 매개변수로 값이 전달될 때, 값이 복사되어 넘어가는 방식이다.
즉, 매개 변수는 스택 영역에 새로운 변수로 할당되고 그 값을 복사 받는다.
반면 Call By Reference는 매개변수로 전달 될 때, 값이 아닌 참조가 넘어가는 방식이다.
전달받는 변수가 메모리 영역에서 가르키는 주소 영역 자체를 넘겨받는다.
예제와 그림을 통해서 어떤 부분이 Call By Reference로 착각하게 만드는지 알아본다.
다음와 같이 테스트 코드를 작성한 후, 세 가지의 케이스에 대해서 출력을 살펴 본다.
public class CallBy {
public static void main(String[] args) {
Person personA = new Person(27);
Person personB = new Person(17);
swapObject(personA, personB);
System.out.println(personA.age +", " + personB.age);
swapValue(personA.age, personB.age);
System.out.println(personA.age +", " + personB.age);
swapValueInObject(personA, personB);
System.out.println(personA.age +", " + personB.age);
}
public static void swapObject(Person personC, Person personD) {
Person tmp;
tmp = personC;
personC = personD;
personD = tmp;
}
public static void swapValue(int valueA, int valueB) {
int tmp2 = valueA;
valueA = valueB;
valueB = tmp2;
}
public static void swapValueInObject(Person personE, Person personF) {
int tmp3;
tmp3 = personE.age;
personE.age = personF.age;
personF.age = tmp3;
}
}
class Person {
int age;
public Person(int age) {
this.age = age;
}
}
코드를 실행해서 출력결과는 다음과 같다. 최종적으로 swapValueInObject() 함수를 실행했을 때만 swap이 발생했다. 자바 객체는 Reference타입이라면서 Call By Reference아니야?..라는 생각이 들 수도 있다. 비슷해 보이지만 어떻게 동작했길래 이런 결과가 나오는지 확인 해본다.
swapObject에서는 인수로 객체 인스턴스를 받아 통째로 바꿔보려고 시도를 했다. 메모리 구조에서는 어떤일이 일어나는지 확인 해본다.
처음 main 함수에서 personA, personB 인스턴스를 생성하면 다음과 같이 personA, personB는 스택 영역에 지역 변수가 할당되고, 힙영역에 할당된 인스턴스들을 가르킨다.
swapObject() 함수를 실행하면 다음과 같이 바뀐다.
스택 영역에 새로운 변수 personC, personD가 생성되고 똑같이 힙 영역에 있는 인스턴스들을 가르킨다.
tmp변수는 personC가 가르키는 인스턴스를 가르킨다.
tmp = personC;
personC = personD;
personD = tmp;
위의 코드를 실행시키면 다음과 같이 바뀐다. personC와 personD가 가르키는 화살표만 바뀌었을 뿐, personA와 personB는 여전히 각각 age가 27, 17인 인스턴스를 가르키고 있다.
메서드가 종료되고 나면 다음과 같이 처음 할당된 모습과 같은 모습 그대로 남는다.
swapValue()가 실행되면 메모리 영역은 다음과 같은 모습을 하게 된다.
primitive 타입의 지역 변수는 스택 영역에 할당된다.
int tmp2 = valueA;
valueA = valueB;
valueB = tmp2;
위의 코드를 실행시키면 다음과 같이 바뀐다. 메서드 내의 지역변수 valueA, valueB에 대해서만 바뀌었지 personA, personB의 age는 그대로다.
마찬가지로 메서드가 종료되면 할당된 메서드의 변수들은 스택영역에서 사라진다.
swapValueInObject()가 실행되면 다음과 같은 메모리 영역 모습이 된다.
swapValue()에서와 같이 PersonE, PersonB 가 각각 힙 영역에 위치한 PersonA, PersonB의 인스턴스를 가르킨다. 그리고 tmp3은 PersonE의 age 값을 그대로 복사한다.
int tmp3;
tmp3 = personE.age;
personE.age = personF.age;
personF.age = tmp3;
그렇다면 위의 코드를 실행 시키면
perosnE가 가르키는 인스턴스의 age는 personF가 가르키는 인스턴스의 age인 17이 된다.
personF가 가르키는 인스턴스의 age는 tmp3. 즉, 27이 된다.
결과적으로 아래와 같은 모습을 하게 된다.
결론은, 기본 타입 변수라면 우리가 흔히 아는 Call By Value의 동작을 하게되고, 참조 타입 변수라면 주소 값을 복사(Call by value)해서 넘긴다는 것이고. 참조형 변수는 주소값을 통해 힙 영역 내부의 인스턴스를 접근할 수 있게 해주는 것이다.
이번에는 자바 코드와 Call By reference를 할 수 있는 C++ 코드를 비교 해본다.
각각 swap을 시도하는 코드를 작성하고 실행 결과를 살펴본다.
public class CallBy2 {
public static void main(String[] args) {
Integer a = 10;
Integer b = 20;
func(a,b);
System.out.println(a+", " + b);
}
static void func(Integer a, Integer b) {
Integer tmp = a;
a = b;
b = a;
}
}
#include <iostream>
using namespace std;
void func(int &a, int &b) {
int tmp = a;
a = b;
b = tmp;
}
int main() {
int a = 10;
int b = 20;
func(a, b);
cout<<a<<","<<b;
}
위에서 봤던 swapObject() 예제와 같이, 매개변수들은 값으로 주소값을 가지고 있을 뿐, 참조할 수 있는 주소를 제시할 뿐 참조하는 것은 아니다.
추가적으로 자바의 Wrapper 클래스는 Object이므로 참조형이지만 immutable 하다. 즉, 새로운 값을 할당 시, 힙 영역의 새로운 주소를 가르킨다.(String도 마찬가지)
public class CallBy2 {
public static void main(String[] args) {
Integer a = 10;
Integer b = 20;
func(a,b);
System.out.println(a+", " + b);
}
static void func(Integer a, Integer b) {
Integer tmp = a;
a = 20;
b = 10;
}
}
반면에 c++에서의 Call by Reference는 메모리 영역의 참조를 전달한다. 그래서 값을 직접 바꿀 수 있는 것이다.
자바는 Call By Value다. 참조 타입이라도 전달되는 것은 주소 값 일뿐이다.