[Java] Call by value, Call by reference (feat. new 연산자)
Call by value / reference를 이햐하기 전에 아래의 코드를 먼저 살펴 봅시다.
아래는 두 변수 값을 swap() 하는 예시 코드입니다.
class CallByValue
package callby;
public class CallByValue {
public static void swap(int x, int y) {
int temp = x;
x = y;
y = temp;
}
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println("swap() 호출 전: a = " + a + " b = " + b);
swap(a, b);
System.out.println("swap() 호출 후: a = " + a + " b = " + b);
}
}
>>> swap() 호출 전: a = 10 b = 20
>>> swap() 호출 후: a = 10 b = 20
당연히 swap() 메소드 전후로 a, b의 값이 바뀌어 출력될 것이란 예상을 엎고 동일한 결과가 출력됐습니다.
왜 일까요?
이번엔 다른 방식으로 a, b값을 swap() 해보는 코드를 살펴보겠습니다.
class CallByRef
public class CallByRef {
int ref;
public CallByRef(int ref) {
this.ref = ref;
}
public static void swap(CallByRef x, CallByRef y) {
int temp = x.ref;
x.ref = y.ref;
y.ref = temp;
}
public static void main(String[] args) {
CallByRef a = new CallByRef(10);
CallByRef b = new CallByRef(20);
System.out.println("swap() 호출 전: a = " + a.ref + " b = " + b.ref);
swap(a, b);
System.out.println("swap() 호출 후: a = " + a.ref + " b = " + b.ref);
}
}
>>> swap() 호출 전: a = 10 b = 20
>>> swap() 호출 후: a = 20 b = 10
처음 살펴본 CallByValue() 클래스와는 다르게, CallByRef() 클래스에선 값이 swap 되어 출력된것을 확인할 수 있습니다.
무엇 때문일까요?
이는 자바의 함수(메소드) 호출 방식이 call by value, 즉 값에 의한 호출을 하기 때문입니다. 이 모호한 말을 이해하기 전에, CallByRef 클래스의 main() 함수에서 생성자를 통해 인스턴스를 생성한 과정을 먼저 살펴 보겠습니다.
New 연산자: new 연산자는 클래스 따위의 객체 생성 역할
- new 연산자를 통해 1) 메모리(heap 영역)에 데이터를 저장할 공간 할당 받고 2) 그 공간의 참조값(reference)을 객체에 반환 (여기서 객체란 아래 그림의 객체변수, a) 합니다.
- 그렇다면, CallbyRef 생성자를 통해 a 객체 변수는 10 이라는 값이 저장된 참조값(혹은 주소값)을 반환 받게 됩니다.
그렇기 때문에 아래의 코드에서 a 객체변수를 출력할 땐 참조 주소값이, a.ref를 출력할 땐 우리가 예상한 10이라는 value값이 출력됨을 확인 할 수 있습니다.
(여기서 int ref 필드는 CallByRef 클래스 첫줄에 선언되어, 생성자 내부에서 값을 할당 받습니다)
public static void main(String[] args) {
CallByRef a = new CallByRef(10);
CallByRef b = new CallByRef(20);
System.out.println("a: " + a + ", a.ref : " + a.ref);
// a에는 참조값만 저장돼 있는 것을 확인 할 수 있다 / a.ref에는 10이라는 int형 필드 값이 저장돼 있다.
}
>>> a: callby.CallByRef@681a9515, a.ref : 10
이런 원리로 swap() 메소드 내부를 확인하면,
public static void swap(CallByRef x, CallByRef y) {
int temp = x.ref;
x.ref = y.ref;
y.ref = temp;
}
// x: callby.CallByRef@681a9515 x.ref: 10
swap() 메소드는 변수 x, y를 스와핑 하는 것이 아닌, x.ref, y.ref 라는 value 값을 스와핑 하여 두 변수의 값을 교환할 수 있게 된다.
이로써 우리는 CallByRef 라는 클래스는 메서드 호출시 사용되는 인자의 주소를 넘겨줌으로, swap() 메소드 내부에서 주소를 참조하여 데이터를 변경할 수 있게 됨을 알 수 있습니다.
이 때, 주소를 참조하여 라는 워딩의 뜻은, 주소에 저장되어있는 값(value)을 참조한다는 뜻이며, 이 값들을 swapping 하여 우리가 원하는 값의 변경을 이뤄 낼 수 있습니다.
말이 길었지만, 우리가 swapping 하는 것은 결론적으로 value, 그리하여 자바는 call by Value의 호출방식을 갖게 됩니다.
Q. 그럼 class CallByValue()도 결국 값에 의한 호출이니, 값의 변경이 있어야 하는 것 아닌가요?
A: 그렇게 생각할 수 있지만, main()메서드 내부에서 정의한 a, b 변수의 값은 결국 main() 메소드의 call by value 를 따르게 됩니다.
아무리 swap() 메소드의 인자로 들어간다 하더라도, a <-> b 간의 swapping은 swap() 메소드 내부에서만 일어날 뿐입니다.
swap() 메소드가 종료된 이후 a, b 변수는 여전히 main() 메소드의 value이기 때문에 기존의 값을 그대로 유지하게 됩니다.
public class CallByValue {
public static void swap(int x, int y) {
int temp = x;
x = y;
y = temp;
System.out.println("x: " + x + " y: " + y);
}
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println("swap() 호출 전: a = " + a + " b = " + b);
swap(a, b);
System.out.println("swap() 호출 후: a = " + a + " b = " + b);
}
}
>>> swap() 호출 전: a = 10 b = 20
>>> x: 20 y: 10
// swap() 메소드 내부에서 x, y값은 swapping되지만, swap() 메소드가 종료되면 x,y는 곧바로 소멸된다.
>>> swap() 호출 후: a = 10 b = 20
reference