티스토리 뷰
1. 자바는 Call By Value(Pass By Value) 방식으로만 동작한다
[ 자바 개발자 제임스 고슬링의 얘기 ]
아래의 내용은 자바 언어의 창시자인 제임스 고슬링(James Gosling)이 집필한 “The Java Programming Language”의 일부 내용이다.
어떤 사람들은 객체가 참조로 전달(by reference)된다고 잘못 말한다. 프로그래밍 언어 설계에서 참조 전달(pass by reference)은 인수가 함수에 전달될 때 그 값의 복사본이 아니라 원래 값의 참조를 전달받음을 의미한다. 만약 함수가 전달받은 매개변수를 변경하면 함수를 호출한 코드도 동일한 메모리 슬롯을 사용하므로 값이 변경되어야 한다.
자바는 객체를 참조로 전달하지 않는다(not pass objects by reference). 대신 객체에 대한 참조를 값으로 전달한다(passes object references by value). 실제 객체를 가리키는 참조의 두 복사본이 동일한 객체를 가리키기 때문에, 한 참조를 통해 만들어진 변경이 다른 참조를 통해서도 보이는 것이다. 자바에는 명확하게도 값을 전달(pass by value)하는 방식만 존재하며, 이것이 단순함을 유지할 수 있게 해준다.
[ 자바 언어의 동작 방식 이해하기 ]
원시 타입을 전달하는 경우
아래의 코드는 원시 타입(Primitive Type) int로 선언된 변수가 있고, add 함수에서 전달 받은 값에 1을 더해주고 있다. 아래의 코드를 실행한 결과는 당연하게도 10이 나온다.
class PassByValueTest {
@Test
void addInt() {
int x = 10;
add(x);
assertThat(x).isEqualTo(10);
}
private void add(int num) {
num++;
}
}
자바에서 원시 타입은 스택 영역에 할당이 된다. 따라서 다음과 같이 x가 스택 영역에 할당될 것이다.
그 다음으로 add 메서드를 호출하고 있다. 자바는 값을 복사하여 전달하기 때문에 add 함수에서 사용될 변수 num이 x를 바탕으로 복사된다. 그리고 num 값을 증가시키므로 메모리에는 다음과 같이 11으로 값이 남아있게 된다.
만약 자바가 값을 전달하는 방식이 아닌 참조를 전달하는 방식으로 동작했다면 x의 값이 증가했을 것이다. 하지만 자바는 값을 복사하여 전달하는 방식으로 동작하기 때문에 기존의 변수 x에는 변화가 없는 것이다.
참조 타입을 전달하는 경우
아래의 코드는 참조 타입(Reference Type)로 선언된 String 배열 변수 strings가 있다. 그리고 add 함수에서는 배열의 1번 인덱스에 값을 할당해주고 있다.
class PassByValueTest {
@Test
void addArray() {
String[] strings = new String[2];
strings[0] = "Hello";
add(strings);
assertThat(strings[1]).isEqualTo("Java");
}
private void add(String[] arrays) {
arrays[1] = "Java";
}
}
자바에서 참조 타입은 힙 영역에 할당이 된다. 그리고 힙 영역을 가리키는 변수 자체(strings)는 스택 영역에 생성되고, 배열은 힙 영역에 할당된다. 이를 그림으로 표현하면 다음과 같다.
그 다음 arrays 변수는 strings에서 복사되어 생성되므로 strings와 별개로 존재한다. 하지만 두 값은 모두 동일한 참조를 가리키고 있다. 따라서 add 함수에서 arrays의 값을 변경하게 되면 strings에서도 영향을 받게 되는 것이다. 이러한 동작은 자바가 값을 전달하는 방식으로 동작함을 보여준다.
[ 요약 및 정리 ]
오해가 생겼던 부분은 Pass By Reference의 참조(Reference)와 자바 언어에서의 참조(Refernece)를 구분하지 않았기 때문인 것 같다. 우리는 이것에 대해 명확히 구분할 필요가 있다.
위에서 설명하였듯 Pass By Reference은 프로그래밍 언어 설계에 대한 개념이다. 프로그래밍 언어에서 Pass By Reference 방식은 실제 인자의 주소가 전달되는 방식이다. 즉, pass by reference는 실제 값에 대한 alias를 구성하여 접근하는 방식이므로 실제 값을 수정하면 원본의 데이터가 영향을 받게 된다.
하지만 자바는 pass by value로 동작하기 때문에 전달된 값을 변경하여도 원본 데이터가 영향받지 않는다. C언어도 마찬가지로 Pass By Value 방식으로 동작하기 때문에 swap 함수를 호출해도 값이 변경되지 않는 것이다.
void main() {
int a = 5;
int b = 10;
swap(a, b);
}
void swap(int x, int y) {
int temp = x;
x = y;
y = temp;
}
자바에서 참조(Reference)라는 개념은 존재하지만, Pass By Reference로 동작하지는 않는다. 많은 개발자들이 참조를 전달하는 방식에 혼란을 겪었기에, 제임스 고슬링은 자바에서 의도적으로 이를 빼버렸다.
자바 언어에서 참조라는 것은 객체가 힙에 저장된 위치를 가리키는 메모리 주소이다. 따라서 참조는 실제 객체에 대한 alias가 아니라 실제 객체에 접근하고 조작하는 방법이라고 볼 수 있다. 다음은 자바 언어 스펙(Java Launguage Specification, JLS)에서 정의한 참조(Reference)에 대한 정의이다.
“참조 혹은 참조 값은 객체에 대한 포인터에 해당하며, 참조할 객체가 없다면 null 참조를 갖는다.”
현실에서는 객체의 참조값을 전달하는 것(pass reference value)를 pass by reference라고 통용하고 있다. 하지만 pass by reference는 프로그래밍 언어의 설계 방식에 대한 내용이고, FORTRAN과 같은 일부 언어들은 해당 방식을 사용한다.
즉, 우리가 사회적으로 얘기하는 pass by reference가 프로그래밍 언어를 설계할 때 사용되는 실제 pass by reference는 아닌 것이다. 이것을 이해할 때 구분할 필요는 있어 보인다.
보다 자세한 원문 내용은 토비님의 페이스북 글을 참고하도록 하자.
참고 자료
- The Java Programming Language 4th edition
- https://www.ibm.com/docs/en/i/7.2?topic=calls-pass-by-reference
- https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html