안녕하세요 사한입니다!
오늘은 함수나 메서드를 호출할 때 인자를 전달하는 방식인
Call by Value 와 Call by Reference
두가지 방식을 자바에서 어떻게 사용하고있는지 알아 보겠습니다.😁
지금까지 Call by Value 와 Call by Reference에 대해 알아보았는데 그렇다면 Java 에서는 어떤 방법을 사용할까요?
Java는 Call by Reference 라고 오해하시기 쉽지만 Java 는 오직 Call by Value 로만 동작합니다.
자바의 Call by Value 동작을 이해하기전에 런타임중 변수 생성 시 메모리에 어떤 식으로 저장되는 지 알아야 합니다.
위 사진은 이전 제 포스트 Jvm과 가비지 컬렉션(GC)을 보셨다면 익숙하실수도 있겠네요😁😁
Jvm과 GC에 대해 설명하면서 보여드린 실행 중 프로그램의 정보가 올라가 있는 Jvm Memory 사진입니다.
우리가 Java 에서 변수를 선언하게 되면 위 사진의 Stack 영역에 할당됩니다.
원시 데이터 타입 (Primitive Data Type) 은 Stack 영역에 변수와 함께 저장되고
참조 데이터 타입 (Reference Data Type) 객체는 Heap 영역에 저장되고, Stack 영역에 있는 변수가 객체의 주소값을 갖고 있습니다.
원시 데이터 타입과 참조 데이터 타입?
원시 데이터 타입
자바에서의 원시 데이터 타입에는 byte, short, int, long, float, double, char, boolean 등이 있습니다.
이들은 스택 메모리에 직접 저장됩니다.
스택 메모리는 컴파일 시간에 메모리 크기가 결정되며, 함수 호출이 끝나면 자동으로 해제되는 특징이 있습니다.
참조 데이터 타입
자바에서의 참조 데이터 타입은 배열, 클래스, 인터페이스 등이 있으며, 이들은 힙 메모리에 저장됩니다.
힙 메모리는 런타임에 동적으로 할당되며, 가비지 컬렉터에 의해 관리됩니다.
참조 데이터 타입의 변수는 스택 메모리에 저장되고, 힙 영역에 생성된 객체를 참조하는 주소값입니다.
이를 그림으로 단순하게 표현하면 다음과 같습니다.
원시 데이터 타입은 Stack 영역에 변수와 함께 저장된다고 했었죠?
코드와 그림을 통해 자바의 동작 방식을 보여드리겠습니다~ 👨🏫
public class PrimitivesUnitTest {
@Test
public void test() {
// ( 1 )
int x = 1;
int y = 2;
// Before
assertEquals(x, 1);
assertEquals(y, 2);
// ( 2 )
modify(x, y);
// After
assertEquals(x, 1);
assertEquals(y, 2);
}
public static void modify(int x, int y) {
// ( 3 )
x += 1;
y -= 1;
}
}
위 코드를 살펴보면 test메서드에 원시 데이터 타입인 int의 변수 x, y 가 modify 메서드 호출 이후 값의 변화를 테스트 하고 있습니다.
실제 과정을 그림을 통해 순차적으로 살펴보겠습니다.
그림의 상황이 발생한 곳을 코드에 주석으로 달아두었으니 확인해보세요!😎
Stack 내부에 test() 와 test()의 변수 x,y가 복사되어 modify() 스택 프레임의 x,y로 따로 들어가게 됩니다.
따라서 modify() 영역의 값이 바뀌어도 test() 영역의 변수는 당연히 변화가 없습니다.
이와 같이 자바에서 원시 데이터 타입의 전달은 값만 전달하는 Call by Value 로 동작합니다.
참조 데이터 타입은 원시 데이터 타입과 다르다는 것은 이미 언급드렸었죠?
변수 자체는 Stack 영역에 생성되지만 실제 객체는 Heap 영역에 위치합니다.
그리고 Stack 에 생성된 변수가 Heap 에 위치한 객체를 바라보고 있는 형태라고 생각하면 좋을 것 같습니다.
마찬가지로 코드와 그림을 통해 알아보겠습니다! 😎
public class PrimitivesUnitTest {
@Test
public void test() {
// ( 1 )
Money x = new Money(100);
Money y = new Money(200);
// Before
assertEquals(x.num, 100);
assertEquals(y.num, 200);
// ( 2 )
modify(x, y);
// After
assertEquals(x.num, 200);
assertEquals(y.num, 200);
}
public static void modify(Money x, Money y) {
// ( 3 )
x.num += 100;
// ( 4 )
y = new Money(300);
// ( 5 )
y.num += 100;
}
}
class Money {
public int num;
public Money(int num) {
this.num = num;
}
}
위 코드를 살펴보면 test메서드에 참조 데이터 타입인 클래스 Money 의 변수 x, y 가 modify 메서드 호출 이후 값의 변화를 테스트 하고 있습니다.
실제 과정을 그림을 통해 순차적으로 살펴보겠습니다.
원시 타입과는 다르게 변수 x, y만 Stack 영역에 생성되고 실제 객체는 Heap 영역에 생성됩니다.
각 변수는 Heap 영역에 있는 객체를 바라보고 있습니다.
modify 메서드는 넘겨받은 파라미터 x,y를 Stack 영역에 생성하고 주소값을 똑같이 바라봅니다.
test() 영역과 modify() 영역에 존재하는 x 변수들은 같은 객체인 Money “x”(x.num=100)
을 바라보고 있기 때문에 객체를 공유합니다.
따라서 modify() 메서드에서 x.num += 100;
를 행하면 값이 변경됩니다.
test() 영역과 modify() 영역에 존재하는 y 변수들은 서로 같은 객체인 Money “y”(y.num=200)
를 바라보고 있었지만
modify() 매서드에서 y = new Money(300);
를 통해 새로운 객체를 생성해서 할당했기 때문에
Money “y”(y.num=300)
이라는 객체를 바라봅니다.
따라서 Money “y”(y.num=300)
의 num 값을 y.num += 100;
을 통해 변경해도 test() 에 있는 y 에는 아무런 변화가 없습니다.
Java 는 참조 데이터 타입이든 원시 데이터 타입이든 Call by Value 로만 동작한다!
해당 포스트에서는 조금 생략하였지만 실제로 Jvm의 Stack의 내부는 조금 더 복잡합니다!
Stack에 stack frame 단위로 추가되는데 stack frame은 Local Variables Array
,Operand Stack
,frame data
로 구성되어 있습니다!
더 정확한 실행 구조는 여러분이 학습해 보면 좋을것 같습니다! 😁😁
https://www.baeldung.com/java-pass-by-value-or-pass-by-reference