Call By Value와 Call By Reference
Call By Value는 함수의 인자를 전달할 때 값을 전달하는 방식으로 실제 인자로 전달되는 값은 복사된 형태로 전달되어 호출자의 원래 변수에는 영향을 미치지 않는다.
Call By Reference 함수의 인자를 전달할 때 값이 아닌 주소를 전달하는 방식으로 Call By Value와 다르게 호출자의 원래 변수에 영향을 미칠 수 있다.
두 방법의 차이는 인자로 값을 넘기냐, 주소를 넘기냐의 차이지만 Java 언어에서는 모든 인자들을 메서드에 전달할 때 값 형식 즉, Call By Value 형태로 전달하게 된다고 한다.
여기서 의문인 점은 왜 모든 부분에서 Call By Value를 사용하는 것일까? 그리고 객체를 넘기면 그게 Call By Reference 아닌가? 이러한 의문점이 들 수 있다.
하나씩 살펴보며 이유를 알아보자.
자바에서 Call By Value
먼저 간단한 예제 코드를 통해서 Call By Value를 알아보자.
public class Ex_1 {
public static void main(String[] args) {
int var = 10;
addValue(var);
System.out.println(var); // var == 10
}
public static void addValue(int var) {
var += 100;
}
}
위의 예제 코드를 보면 변수 var은 처음에 10으로 초기화된 이후에 해당 값을 addValue() 메서드의 인자로 넘겨주었다.
그 후에 addValue 메서드에서 해당 var 값에 100을 더했지만 main 메서드에서 var을 출력해 보면 처음 초기화 되었던 10이 출력되는 것을 알 수 있다.
이렇게 되는 이유를 알기 위해서는 값들이 메모리에 어떻게 저장되는지 알아야 한다.
먼저 프로그램이 실행되면 main 메서드가 stack 영역에 저장되고 그 안에 변수 var이 저장된다.
그 다음 addValue() 메서드를 호출했을 경우 stack 영역에 해당 메서드가 저장되면서 전달받은 var 값에 100을 더한다.
여기서 자세히 봐야 할 부분은 var 변수가 어떤 메서드에 있는지를 알아야 한다.
위의 그림과 같이 똑같은 var 변수를 사용하는 것처럼 보이지만 실제로는 각각의 메서드에 있는 독립적인 var 변수인 것을 알 수 있다.
addValue() 메서드의 실행이 종료되면 stack 영역에서 해당 메서드가 지워지게 되면서 이전에 100을 더했던 var 변수도 같이 사라지게 된다.
따라서 기존의 var은 아무런 영향이나 변경 없이 기존의 10 값을 출력하게 되는 것이다.
여기까지 살펴본 것은 자바에서 원시 타입에 대한 Call By Value이다. 그렇다면 객체 타입은 어떻게 Call By Value가 이뤄지는지 알아보자.
먼저 간단한 예제 코드를 살펴보자.
public class Ex_2 {
public static void main(String[] args) {
Value value = new Value(10);
addValue(value);
System.out.println(value.number); // number == 110
}
public static void addValue(Value value) {
value.number += 100;
}
}
class Value {
int number;
Value(int number) {
this.number = number;
}
}
이전의 원시 타입에서 예제 코드와는 다르게 실제로 값에 100이 더해져서 출력되는 것을 알 수 있다.
그렇다면 실제 호출자의 변수 값이 변경되었으니 Call By Reference라고 할 수 있을까?
해당 예제 코드가 메모리에서 어떻게 동작하는지 하나씩 살펴보자.
먼저 main 메서드에 Value 객체를 생성하게 되면 위의 그림과 같이 Heap 메모리 영역에 객체가 생성되고 해당 객체의 주소 값을 value 변수에 저장하게 된다.
그 다음 addValue 메서드를 호출하게 되면 Stack 메모리 영역에 addValue 메서드가 저장되면서 매개변수로 받은 Value 객체는 똑같은 주소 값을 가지게 된다.
여기서 위의 그림을 잘 살펴보면 이전에 원시 타입을 넘겼을 때 Call By Value와 똑같다는 것을 알 수 있다.
그림을 합쳐서 비교해 보면 인자로 값을 넘기나 주소 값을 넘기나 값이 그대로 전달되는 것이 아닌 복사가 된다는 것을 알 수 있다.
객체 타입도 마찬가지로 객체의 주소를 참조하는 값을 복사한 형태로 전달할 뿐 절대 그대로 전달하지 않는다.
예제 코드를 계속해서 진행해보면 addValue 메서드를 통해 Value 객체의 number 변수 값에 100을 더해주고 해당 addValue 메서드는 종료된다.
그다음 최종적으로 Value 객체의 number 값을 출력해 보면 110이 출력된다.
결과만 보면 Value 객체의 number 변수가 변경되어서 Call By Reference가 된 것 같지만 실제로는 주소 값을 복사해서 전달한 Call By Value 형식으로 진행된 것을 확인할 수 있다.
그렇다면 진정한 Call By Reference는 대체 무엇일까?
- C언어에서 Call By Reference
진정한 Call By Reference는 C언어의 포인터를 통해 알 수 있다.
앞서 살펴봤던 자바에서 객체 타입의 Call By Reference를 다시 보면 똑같은 Value 객체를 가리키고 있을 뿐 실제 value의 주소는 다르다는 것을 알 수 있다.
하지만 C언어에서 포인터를 사용하게 되면 주소 값 자체를 그대로 인자로 전달할 수 있기 때문에 기존의 main 메서드의 value 변수나 addValue 메서드의 인자로 전달되는 value 변수는 똑같은 주소 값을 가지게 되는 것이다.
자바에서 포인터 개념이 없는 이유
자바에서 C언어에 있는 포인터 개념이 없는 이유가 뭘까?
포인터를 통해 실제 주소 값을 그대로 전달하여 사용하면 추가적인 복사본이 필요하지 않기 때문에 성능적인 부분이나 효율적인 면에서는 좋을 수도 있다.
하지만 포인터를 통해 실제 메모리 주소를 다루는 것은 근본적으로 안전하지가 않다. 왜냐하면 해당 주소 값을 통해 메인 메모리에 직접 접근하는 것이 가능하기 때문이다.
또한 C언어나 C++ 처럼 포인터를 사용하는 언어를 다뤄보면 메모리를 개발자가 직접 할당하고 해제해야 하기 때문에 메모리 누수가 발생할 수 있는 문제가 있다.
자바에서는 JVM을 통해서 메모리를 자동으로 관리해 주고 필요 없는 객체는 GC가 알아서 제거해 주기 때문에 개발자가 직접 다룰 일이 없어 온전히 개발에만 집중할 수 있는 이점이 있다.
참고 자료
https://www.baeldung.com/java-pass-by-value-or-pass-by-reference
'자바' 카테고리의 다른 글
[JAVA] - Mutable vs Immutable (0) | 2024.09.09 |
---|---|
[Java] - GC(Garbage Collection) (1) | 2024.08.29 |
Java - JVM(Java Virtual Machine) (0) | 2023.12.21 |
Java - 스레드 (1) | 2023.11.29 |
Java - 입출력 스트림 (0) | 2023.11.29 |