Call by Value? Call by Reference? 그게 뭔데?
Call by Value와 Call by Reference는, 메서드를 호출할 때 파라미터를 전달하는 방법을 말한다.
Call by Value
- 메서드를 호출할 때 전달되는 파라미터로 "값"을 넘겨준다. 이러한 이유로 "Pass by Value" 라고도 불린다
- 호출하는 호출자(Caller)의 변수 "값"을 복사하여, 수신하는 수신자(Callee)의 파라미터로 넘겨준다
- 메서드를 호출하는 쪽(호출자)의 변수와 호출 당하는 쪽(수신자)의 파라미터는 같은 "값"이지만, 주소가 다른 서로 다른 변수이다
- 복사되어 넘어간 파라미터는, 호출 함수 안에서 지역적으로 사용되는 지역변수이다
- 오직 "값" 만을 전달하기 때문에, 호출자와 수신자 어느 한쪽에서 변경이 일어나더라도 다른쪽에서는 변화가 없다
Call by Reference
- 메서드를 호출할 때 전달되는 파라미터로 "주소"를 넘겨준다. 이러한 이유로 "Pass By Reference" 라고도 불린다
- 호출당하는 수신자의 변수 "주소"를 그대로 호출자의 파라미터로 넘겨준다
- 호출자의 변수와 수신자의 파라미터는 같은 "값"이고 주소도 동일한, 완벽하게 동일한 변수이다
- 같은 주소에 위치한 완벽하게 동일한 변수이므로, 호출자와 수신자 어느 한쪽에서 변경이 일어나면 다른쪽에도 변경이 일어난다
Example)
- main이 호출자, func이 수신자, 파라미터가 n이다
- n은 변수의 "값" 이고 지역변수로 사용된다
- func에서 n=20으로 변경되어도, main에서 n=10으로 유지된다
public class Fuctions {
public static void main(String[] args) {
int n = 10;
func(n);
System.out.println(n);
}
public static void func(int n) {
n = 20;
}
}
그렇다면 Java에서는 어떤 방식을 사용할까?
Java에서는 변수의 주소 자체를 가져오거나 넘겨줄 방법이 없어서 항상 "Call By Value"로 "값"을 넘겨준다.
하지만 reference type(참조 자료형)을 넘길 시에 해당 객체의 주소"값"을 복사하여 이를 사용하기 때문에 원본 객체의 프로퍼티(속성)까지 접근이 가능해서 Call by Reference로 오해하기 쉽다. 이는 어디까지나 프로퍼티까지만 접근 가능하고, 원본 객체 자체를 변경할 수는 없다는 것에 유의해야한다. Java에서 데이터를 넘기는 과정을 통해 알아보자.
JVM 메모리에 변수가 저장되는 위치
- Java의 데이터 형태
- Java의 원시형(primitive type) : boolean, short, int, long, float, double, char
- Java의 참조형(reference type) : Class, Interface, Array, Enum, 원시형을 제외한 모든 것들
- Java에서 변수를 선언하면 stack 영역에 할당된다
- 원시 타입 객체는 stack 영역에 변수와 함께 저장
- 참조 타입 객체는 Heap 영역에 저장되고, stack 영역에 있는 변수가 객체의 주소값을 가짐
원시 타입을 넘기는 경우
- primitive type을 파라미터로 넘길 때, 그 "값" 자체만 넘어가고 "주소"가 넘어가는 것이 아님
- 따라서 넘겨받은 "값"을 바꾸더라도, stack 영역에 존재하는 원시타입은 변하지 않음
- 즉 primitive type의 전달은, "값"만 전달하는 Call by Value이다
public class primitiveType {
@Test
void test() {
int a = 1;
int b = 2;
// 변수를 넘겨서 바꾸기
modify(a, b);
// 확인
assertEquals(a, 1); // == false
assertEquals(b, 1); // == false
}
public static void modify(int a, int b) {
// 여기 위치한 a, b는 이름만 같고 주소가 다르다. 따라서 test의 a,b와는 완전 다른 변수이다
a = 5;
b = 10;
}
}
참조 타입을 넘기는 경우
- Reference Type 객체는 heap 영역에 위치하고, stack 영역에 위치한 변수가 Reference Type 객체를 바라보고 있는 형태이다
- 참조 타입을 파라미터로 넘겨주더라도, 넘겨받은 메소드 안에 위치한 참조 타입의 변수와 기존 위치의 변수는 다른 스택에 위치한 "다른 변수" 이다
- "값"만 동일한 서로 다른 두 변수는, 하나의 Reference Type 객체를 공유하고 있는 것이지 동일한 것이 아니기에 Call by Value로 동작한다 !!
- 새로운 객체를 할당하더라도 원본 변수가 영향을 받지 않는다는 것에 집중하면 쉽게 이해할 수 있을 것이다
class User {
public int age; // User 변수
public User(int age) { // 생성자
this.age = age;
}
}
public class ReferenceTest {
@Test
void test() {
User a = new User(10); // User01 이라고 가정
User b = new User(20); // User02 라고 가정
// 수정
modify(a, b);
// 확인
assertEquals(a.age, 11); // == true -> 바뀌었다고 Call by Reference가 아님
assertEquals(b.age, 20); // == true -> 여기의 b는 User02이다
}
private void modify(User a, User b) {
// modify에서 수정이 일어나는 age 변수는 modify 스택에 존재하는 변수이다
// test의 스택에 존재하는 age 변수와는 다르다 !!
// 하지만 동일한 User라는 Reference Type을 공유하기에, 변경이 동시에 일어날 뿐이다
a.age ++;
// b에 새로운 객체를 할당하면, 가리키는 Reference Type 자체가 달라지기에 원본에는 영향이 없다
b = new User(30); // User03 이라고 가정
b.age ++;
}
}
Reference
- Call by value와 Call by reference : https://gyoogle.dev/blog/computer-language/Java/Call%20by%20value%20&%20Call%20by%20reference.html
- Java의 Call by Value, Call by Reference : https://bcp0109.tistory.com/360