티스토리 뷰
메소드(함수) 호출 방식
프로그래밍 언어에서 변수를 다른 함수의 인자로 넘겨 줄 수 있습니다. 이 때 이 변수의 '값'을 넘겨 주는 호출 방식을 Call by Value, 이 변수의 '참조값' (혹은 주소, 포인터)를 넘겨 주는 호출 방식을 Call by Reference라고 합니다. (이외에도 Call by Assignment, Call by Name 등의 개념이 있다고 들었습니다.) 자바는 Call by Value 방식으로 동작하게 되는데 이를 증명 해 볼 수 있는 대표적인 예제가 변수의 값을 바꿔보는 함수(메소드)가 있습니다.
public static void main(String[] args) {
int a = 1;
int b = 2;
swap(a, b);
System.out.println(a); //출력결과 1
System.out.println(b); //출력결과 2
}
static void swap(int a, int b) {
int tmp = a;
a = b;
b = tmp;
}
위와 같이 두개의 변수를 간단히 바꿔보는 메소드를 만들어서 실행 후 출력 해보면, 인자로 넘겨주었던 변수들의 값이 변경 되지 않고, main() 에서는 그대로 출력 되는 것을 알 수 있습니다. 자바의 함수(메소드) 호출 방식이 Call by Value 이기 때문입니다. (만약 Call by Reference 였다면 값이 바뀌게 됩니다.)
자바의 참조형은 Call by Reference 인가?
자바의 함수(메소드) 호출 방식에 대해서 공부하다가 꽤 흥미로운 사실을 알게 되었습니다. 자바의 기본형은 Call by Value가 맞지만, 참조형은 Call by Reference가 맞다는 의견도 있고 / 기본형과 참조형 모두 Call by Value가 맞다는 의견도 있었습니다. 또한 중립적으로 이견이 있음을 인정하는 의견도 있었습니다. 왜 이런 이견이 생기는 걸까요?
1. 참조 바꿔보기
어떠한 부분이 자바 개발자들에게서 이견이 생기는지 코드로 알아 보도록 하겠습니다.
class MyClass{
int index;
public MyClass(int index) {
this.index = index;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}
public class JavaCallByValue {
public static void main(String[] args) {
MyClass myClass1 = new MyClass(1);
MyClass myClass2 = new MyClass(2);
//두개의 참조를 바꿔 보자
swapReference(myClass1, myClass2);
System.out.println(myClass1.getIndex()); //출력결과 1
System.out.println(myClass2.getIndex()); //출력결과 2
}
static void swapReference(MyClass m1, MyClass m2) {
MyClass tmp = m1;
m1 = m2;
m2 = tmp;
}
}
필드가 하나 있는 간단한 클래스(MyClass)를 선언했습니다. 그리고 MyClass 2개를 생성하고 각각의 index 필드 값을 1과 2로 지정 했습니다. swapReference() 메소드를 이용해서 두개의 참조를 바꿔 보면, 필드 값이 바뀌지 않고 1, 2로 출력되게 됩니다. 이런 부분을 보면 자바가 Call by Reference가 아님을 알 수 있습니다. 참조형을 사용 하더라도, 두개의 참조를 바꾸는 일은 안되기 때문입니다. 만약 진정한 Call by Reference라면 참조가 바뀌는 일도 가능해야 합니다.
2. 필드 값 바꿔보기
public class JavaCallByValue {
public static void main(String[] args) {
MyClass myClass1 = new MyClass(1);
MyClass myClass2 = new MyClass(2);
//두개의 필드 값을 바꿔 보자
swapValue(myClass1, myClass2);
System.out.println(myClass1.getIndex()); //출력결과 2
System.out.println(myClass2.getIndex()); //출력결과 1
}
static void swapValue(MyClass m1, MyClass m2);{
int tmpIndex = m1.getIndex();
m1.setIndex(m2.getIndex());
m2.setIndex(tmpIndex);
}
이번에는 참조가 아닌 각각의 필드 값을 Getter/Setter를 이용해서 바꿔보았습니다. 그런데, 예외적으로 이런 경우에는 두개의 값이 변경됩니다. 이런 부분 때문에 참조형이 Call by Reference라는 오해를 받게 된다고 생각합니다. 왜 이런 부분이 가능할까요? 자바가 함수의 인자로 전달해주는 것은 어떤 것을 참조 하고 있는지에 대한 (복사된) 참조 값을 전달하기 때문입니다. 1번 예제에 경우 (복사된) 참조값을 받아서 그 참조값들을 다른 스택(함수)에서 변경하는건 아무런 의미가 없습니다. 하지만 참조값을 받아서 그 참조값을 이용하는 것은 가능하기 때문입니다. 자바에서 객체를 컨트롤 하는 행위는 어떤 장소이든 간에 그 객체를 참조하는 참조값만 알고 있다면 가능합니다. (접근제어자로 막혀있지 않은 한) 그래서 2번 예제에 같은 경우가 가능한 것 입니다.
결론
자바의 참조형이 Call by Value냐 Call by Reference냐 라는 물음에 대한 답은 Call by Value가 맞는 것 같습니다. 그런데 제가 공부하면서 느낀 건 자바의 참조형이 Call by Reference인지 아닌지 엄밀히 구분할려는 것 자체가 무의미 하다는 것입니다. 이런 혼동의 원인은 아마도 C언어에 익숙한 프로그래머 들의 잔재(?)가 아닐까 합니다. 애초에 C언어에서는 Call by Value와 Call by Reference를 문법적으로 구분이 가능하지만, 자바는 문법적으로 구분이 불가능 합니다. 자바의 메소드(함수) 호출 방식은 그냥 자바의 호출 방식으로만 이해하면 됩니다.
+ (19.02.21) 도서 '자바의신 (이상민 저자)' 에서는 동사로 Call을 사용하지 않고 Pass를 사용하고 있습니다, 동사는 다르지만 메소드의 입장인지, 인자의 입장인지에 차이일 뿐 개념적으로는 Call과 Pass 모두 동일한 의미입니다.
'Java' 카테고리의 다른 글
자바는 왜 다중상속을 지원하지 않을까? (다이아몬드 문제) (9) | 2019.02.24 |
---|---|
자바(JVM)의 메모리 사용 방식 (T 메모리 구조) (7) | 2019.02.21 |
For-each문은 For문 보다 얼마나 빠를까? (5) | 2019.01.25 |
자바의 Default Method (디펄트 메소드) (3) | 2019.01.21 |
Optional 클래스 - Null 데이터 편하게 다루기 (0) | 2019.01.18 |