말 그대로 값에 의한 호출, 메서드 호출 시 값을 넘겨준다.
메서드를 호출하는 호출자 (Caller) 의 변수와 호출 당하는 수신자 (Callee) 의 파라미터는 복사된 서로 다른 변수이고 수신자의 파라미터를 수정해도 호출자의 변수는 수정되지 않는다.
메서드 호출 시 참조 (주소) 를 직접 넘겨준다.
호출자의 변수와 수신자의 파라미터는 완전히 동일한 변수이고 수신자의 파라미터를 수정 시 호출자의 변수 또한 수정된다.
결론적으로 말하자면 자바는 Call By Value 방식으로만 동작한다. 이를 이해하기 위해서는 자바에서 변수를 생성할 때 어떤 방식으로 저장되는지를 이해해야한다.
Java에서 변수를 선언하면 Stack 영역에 할당된다.
이때 원시 타입(Primitive Type)은 Stack 영역에 변수와 함께 저장되고
참조 타입(Reference Type)은 Heap 영역에 객체가 Stack 영역에 객체의 주소값이 저장된다.
원시 타입은 위에서 확인한 것과 같이 Stack 영역에 위치한다.
@Test
@DisplayName("Primitive Type 저장")
void primitiveValueTest() {
int a = 1;
int b = 2;
// Before
assertEquals(a, 1);
assertEquals(b, 2);
modify(a, b);
// After
assertEquals(a, 1);
assertEquals(b, 2);
}
private void modify(int a, int b) {
a = 3;
b = 4;
}
다음과 같이 a,b 변수를 modify 파라미터로 넘겨주고 해당 값을 바꾼다한들 원래 변수는 바뀌지 않는다.
이는 파라미터로 a,b 변수가 넘어갈때 스택 영역에 새로운 공간이 생기고 해당 공간에 파라미터가 신규로 저장되기 때문이다.
그렇다면 참조 타입의 경우는 어떨까?
사실 Java가 Call By Reference라고 착각하게 하는 가장 큰 이유가 이 참조 타입 때문이다.
class User {
public int age;
public User(int age) {
this.age = age;
}
}
@DisplayName("Reference Type 저장")
@Test
void referenceValueTest() {
User a = new User(10);
User b = new User(20);
// Before
assertEquals(a.age, 10);
assertEquals(b.age, 20);
modifyObject(a, b);
// After
assertEquals(a.age, 11);
assertEquals(b.age, 20);
}
private void modifyObject(User a, User b) {
a.age++;
b = new User(30);
b.age++;
}
위 코드를 보면 modifyObject 메서드에서 User a의 age를 증가시키니 referenceValueTest()의 a.age도 증가하였다. 이를 보고 '아 참조 타입의 경우 Java도 주소값을 넘기네 그럼 Call By Reference겠다' 라고 생각하는데 이는 잘못된 생각이다.
Call By Reference는 파라미터로 참조 자체를 넘기기 때문에 파라미터에 새로운 객체를 할당하면 원본 변수도 동시에 영향을 받는다.
하지만 Java 참조 타입의 경우 파라미터의 새로운 객체를 할당하면 원본 객체와 무관하게 새로운 객체를 할당한다.
Java에서는 파라미터가 원본 객체의 프로퍼티까지는 접근이 가능하나, 원본 객체 자체를 변경할 수는 없다.
자바는 Call By Value 방식으로만 동작한다.
Call by value의 경우, 데이터 값을 복사해서 함수로 전달하기 때문에 원본의 데이터가 변경될 가능성이 없다. 하지만 인자를 넘겨줄 때마다 메모리 공간을 할당해야해서 메모리 공간을 더 잡아먹는다.
Is Java "pass-by-reference" or "pass-by-value"?
Java 의 Call by Value, Call by Reference