오늘도 역시 java에 대해서 이야기 해볼려고 한다. java로 먹고 살아야 하니..
call by value와 call by reference
오늘 내용은 제목에도 있다시피 java는 call by value?
, call by reference?
를 알아볼 예정이다. 그 전에 아주 간단하게 call by value와 call by reference에 대해서 살짝 살펴보자. 보통 c 보다 자바를 먼저 공부한 개발자는 들어본 개발자도 있을 것이고 듣지 못한 개발자도 있을 수 있다. 왜냐하면 책에서 딱히 다루지 않기 때문이다. 필자도 자바책에서는 거의 못본.. 사실 자바만 다루는책은 두권정도밖에 없다. 그래서 못본거일 수도.. 하지만 c와 c++을 먼저 공부를 했다면 무조건 등장한다. 잠시 c언어 책을 꺼내고 살펴보도록 하자.
call by value (값에 의한 호출)
call by value는 가장 일반적인 함수 호출형태로 값을 복사하는 것이다. 예를 들어 보자.
int add(int a, int b)
{
return a + b;
}
c 의 함수의 기본적인 형태이다. (혹시 다음에 나올 문법이 c++이라도 그냥 c라고 이야기 하겠다.) add 메서드를 호출하면 a + b를 더한 값을 리턴한다. 호출 해보자.
int a1 = 10;
int a2 = 20;
cout << add(a1, a2) << endl;
값은 당연히 30이 출력 될 것이다. 여기서 변수 a1과 a2는 add() 함수의 a, b와는 완전 별개의 변수가 된다. 즉 값이 복사되는 것이다. 그래서 만약 add 함수의 a와 b를 변경 하더라도 a1와 a2는 영향을 받지 않는다. 뭐 당연한 말을 이렇게 하나 싶은데 좀 더 살펴보자.
call by value와 call by reference가 나오면 단골로(?) 나오는 예제를 살펴보자. 예상했겠지만 swap함수를 만들어보자.
void swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
뭐 어려운 부분은 없다. 두 변수를 바꾸는 함수이다. 한번 호출해보자.
int a1 = 10;
int a2 = 20;
swap(a1, a2);
cout << "a1: " << a1 << " a2: " << a2 << endl;
출력 해보면 a1: 10 a2: 20
이와 같이 출력 될 것이다. 아까 위에서 a1과 a는 별개의 변수인 것이 증명 되었다. 뭔 당연한 소리를 이렇게 길게 이야기 하고 있어? 라고 할 수도 있으니 빨리 다음으로 넘어가자.
call by reference (참조의 의한 호출)
call by reference 를 자세히 이야기하면 글이 너무 길어지므로 간단하게만 이야기 해보자. 포인터에 대해서도 잘알고 해야되는데 여기에서는 그게 중요한 내용이 아니므로 모르는 사람이 있다면 그냥 이런게 있다고만 알고 있자.
간단하게 한줄로 요약하자면 변수의 주소를 전달하는 것이다. 다시 swap 함수를 call by reference 로 만들어보자.
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int a1 = 10;
int a2 = 20;
swap(&a1, &a2);
cout << "a1: " << a1 << " a2: " << a2 << endl;
호출하면 아까 위의 call by value
와는 다르게 a1: 20 a2: 10
두개의 변수가 바뀌어 출력 된다. 이게 바로 call by reference 참조의 의한 호출이다. 포인터 변수 a와 b에는 각각의 a1의 주소 a2의 주소가 전달된다. call by value 처럼 값을 복사하는 것이 아니고 실제 주소를 전달하여 swap 함수 내에서 조작을 하면 실제 값도 변경 될 수 있는 것이다.
이정도면 아주 정확히는 몰라도 어느정도 두개의 차이점을 알아봤다. 그렇다면 과연 java는 call by reference 일까?
java는 call by reference가 아니다.
결론부터 말하자면 java는 항상 call by value이다. 흔히 java의 오해를 살 수 있는 부분을 살펴보자.
public class CallByValue {
public static void main(String[] args) {
Person p = new Person("wonwoo");
System.out.println("p.name: " + p.name);
callByValue(p);
System.out.println("p.name: " + p.name);
}
public static void callByValue(Person p) {
p.name = "kevin";
}
}
class Person {
String name;
public Person(String name) {
this.name = name;
}
}
위의 코드를 출력해보면 아래와 같이 출력 된다.
p.name: wonwoo
p.name: kevin
읭? 바뀌었는데? 그럼 자바도 call by reference가 아닐까? 오해의 소지는 여기서 발생했다. 실제 상태값을 바꾸는 것에서 오해가 시작되었다. call by reference라면 상태를 변경하는게 아니라 실제 callByValue 함수의 p에 다른 Person 객체를 넣어 바뀐다면 그게 call by reference가 되는 것이다. 다음과 같이 말이다.
public class CallByValue {
public static void main(String[] args) {
Person p = new Person("wonwoo");
System.out.println("p.name: " + p.name);
callByValue(p);
System.out.println("p.name: " + p.name);
}
public static void callByValue(Person p) {
p = new Person("kevin");
}
}
class Person {
String name;
public Person(String name) {
this.name = name;
}
}
callByValue
메서드부분만 바뀌었다. callByValue
메서드를 보면 이름을 kevin
으로 다시 생성해서 할당한다. 만약 자바가 call by reference
라면 아까와 동일하게 출력 되어야만 한다. 하지만 이 코드를 출력해보면 다음과 같다.
p.name: wonwoo
p.name: wonwoo
실제로 Person은 변경되지 않았다. call by reference가 아닌 또다른 이유는 자바에서는 객체의 주소를 가져오는 방법이 없다. 만약 call by reference 지원한다면 주소를 가져오는 방법을 지원해야 할 것인데 말이다.
그럼 우리는 call by reference가 어떤건지 보자. c는 call by reference 를 지원하니 c코드를 보자. 정확히는 c++.. 오랜만에 c로 코딩할려니 문법조차 다 까먹었다.. Xcode도 거의 4년? 5년만에 열었..
#include <iostream>
using namespace std;
class Person
{
public :
string name;
Person (string name) {
this->name = name;
}
};
int add(int a, int b)
{
return a + b;
}
void callByReference(Person *p)
{
*p = Person("kevin");
}
void callByValue(Person p)
{
p = Person("kevin");
}
int main(int argc, const char * argv[])
{
Person p("wonwoo");
cout << "p.name: " << p.name << endl;
callByReference(&p);
// callByValue(p);
cout << "p.name: " << p.name << endl;
return 0;
}
아까 자바와 동일하게 만들었다. 실제 callByReference() 함수를 호출하면 실제 바뀐 내용이 출력 되지만 callByValue() 함수를 호출할 경우에는 자바와 동일하게 wonwoo
가 두 번 출력 된다. 이로써 java는 call by reference
가 아니라는게 증명? 되었을 것이라고 생각된다.
오늘은 이렇게 자바의 call by value와 call by reference(?) 에 대해서 알아봤다. 만약 call by value와 call by reference에 대해서 더 자세히 알고 싶다면 인터넷으로 찾길 바란다. 필자는 역량은 여기까지다.. ㅜㅜ
자바가 call by reference라는 오해를 할 수 있다. 필자 역시 초보 시절에는 자바가 call by reference가 되는 줄 알았기 때문이다. 어떻게 읽고 쓰냐에 대해서 오해의 소지가 있을 수 있으니 조심해야 될 듯 싶다.
오늘은 여기서 이만..
10 Comments
으아앙
어렵네요.
wonwoo
그러게요…
헤이효
감사합니다 이해하는데 도움이 되었습니다~
wonwoo
감사합니다.
C++
c++ 예제에서 *p = Person statement를 조금 수정하셔야 할 것 같습니다. 현재 문법은 흔히 말해지는 Undefined behavior입니다. new operator를 붙이지 않고 class의 생성자만을 불러 생성된 object는 local object와 같은 형태를 가지게 되기 때문입니다. function이 종료될 때 terminate될 local object의 address를 상위 포인터가 가지게 될 경우 오류를 야기할 수 있습니다.
wonwoo
아항 그런가요? 제가 잘 몰랐네요..
제가 관련해서 공부좀 한 뒤 수정하겠습니다.
좋은 지적 감사합니다.
데비루만
배열과 클래스를 인수로 넘겨주면 call by reference라고 알고있습니다. java는 항상 call by value라고 하셨는데 님의 예제만 봐도 실제로 클래스의 인스턴스 변수가 바꼈죠. 그렇게 할수 있는 유일한 방법은 포인터를 넘겨준것이고 그게 call by reference 아닌가요? 저도 학생이라 정확히 알고싶어서 댓글 남깁니다.
내멋으로
자바가 call by value, call by reference 둘다 지원한다고 이제까지 믿고 있었네요.
프로젝트 들에서
위 예제의 1번과 같은 코드는 많이 사용하지만
2번 과 같은 코드는 별로 사용하지 않았던 모양입니다
좋은 글 읽고 갑니다
감사합니다
Declan Andrew
포인터 잘못쓰신거같습니다.
정의부에서 포인터* 연산은 참조하고있는 주소의 값을 할당하거나 바꿀때 사용하는 것입니다.
참조연산빼고 p = new person하면 kevin 안찍습니다.
매개변수는 메모리의 주소를 복사해와서 스택에 할당되고 함수 끝날때까지 라이프사이클을 유지합니다.
결과를 보면
자바의 연산은 Call by reference가 맞습니다.
organism
https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value?answertab=votes#tab-top