✅ 값에 의한 전달(Call by Value)
값에 의한 전달은 메소드에 파라미터로 전달되는 값의 복사본을 전달하는 방식입니다. 메소드 내에서 전달된 값을 변경하더라도 원본 변수에는 영향을 주지 않습니다. Call by Value는 메서드를 호출할 때 값을 넘겨주기 때문에 Pass by Value 라고도 부릅니다. 메서드를 호출하는 호출자 (Caller) 의 변수와 호출 당하는 수신자 (Callee) 의 파라미터는 복사된 서로 다른 변수입니다.
public class Main {
public static void main(String[] args) {
int x = 10;
System.out.println("Before method call: " + x);
changeValue(x);
System.out.println("After method call: " + x);
}
public static void changeValue(int value) {
value = 20;
System.out.println("Inside method: " + value);
}
}
출력 결과:
Before method call: 10
Inside method: 20
After method call: 10
위의 예시에서 changeValue
메소드는 value
라는 파라미터를 갖고 있습니다. changeValue
메소드는 전달받은 value
의 값을 20으로 변경하고 출력합니다. 하지만 main
메소드에서 x
를 출력해보면 메소드 호출 전후로 값이 변하지 않음을 확인할 수 있습니다. 이는 changeValue
메소드에 x
의 복사본이 전달되었기 때문입니다.
✅ 참조에 의한 전달(Call by Reference)
참조에 의한 전달은 메소드에 파라미터로 전달되는 변수의 참조를 전달하는 방식입니다. 즉, 원본 변수의 주소값을 전달하게 됩니다. 이 경우 메소드 내에서 전달된 참조를 통해 변수의 값을 변경할 수 있으며, 변경된 값은 원본 변수에도 영향을 줍니다. Call by Reference 는 참조(주소)를 직접 전달하며 Pass By Reference 라고도 부릅니다. 참조를 직접 넘기기 때문에 호출자의 변수와 수신자의 파라미터는 완전히 동일한 변수입니다.
public class Main {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
System.out.println("Before method call: " + Arrays.toString(arr));
changeArray(arr);
System.out.println("After method call: " + Arrays.toString(arr));
}
public static void changeArray(int[] array) {
array[0] = 10;
System.out.println("Inside method: " + Arrays.toString(array));
}
}
출력 결과:
Before method call: [1, 2, 3]
Inside method: [10, 2, 3]
After method call: [10, 2, 3]
위의 예시에서 changeArray()
메소드는 array라는 int 배열을 파라미터로 받습니다. changeArray()
메소드는 배열의 첫 번째 요소를 10으로 변경하고 출력합니다. main()
메소드에서 배열을 출력해보면 메소드 호출 이후에도 배열의 값이 변경되었음을 확인할 수 있습니다. 이는 changeArray()
메소드에 배열의 참조, 즉 원본 배열의 주소값이 전달되어 원본 배열을 직접 참조할 수 있기 때문입니다.
Call by Value : 원본 변수의 값을 복사해서 전달, 원본 변수 값의 영향❌ Call by Reference : 원본 변수의 주소값을 전달, 원본 변수 값의 영향⭕
✅ Java에서는 Call by Reference가 없다
Java에서도 메소드의 매개변수로 참조형 변수를 넘겨주어, 주소를 통해 참조하는 원본 객체의 값을 변경할 수 있으므로 Call by Reference가 있다고 생각할 수 있습니다. 그러나 객체가 인수로 전달될 때마다 힙 메모리에서 원래 참조 변수와 동일한 객체의 주소를 가리키는 참조 변수 복사본이 생겨 납니다. 그 결과 메소드에서 동일한 객체를 참조하기 때문에 복사본의 값을 변경할 때마다 해당 변경 사항이 원래 객체에 반영됩니다.
마치 Call by Reference처럼 동작하는 이유는 참조 변수 복사본이, 원본 참조 변수와 동일한 객체를 가리키기 때문입니다. 하지만 참조 변수 복사본에게 새로운 객체를 할당한다면 참조 변수 복사본과 원본 참조 변수는 서로 다른 객체를 가리키게 되므로 메소드 내에서의 값 변경은 더 이상 원본 참조 변수에게 영향을 받지 않습니다.
package spring.board.learning;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class People {
public int age;
public People(int age) {
this.age = age;
}
}
public class ReferenceTypeTest {
@Test
void testReference() {
// given
People a = new People(10);
People b = new People(20);
assertEquals(a.age, 10);
assertEquals(b.age, 20);
// when
modify(a, b);
// then
assertEquals(a.age, 11);
assertEquals(b.age, 20);
System.out.println("a의 참조 주소 " + a.hashCode());
System.out.println("b의 참조 주소 " + b.hashCode());
}
private void modify(People a, People b) {
a.age++;
b = new People(30);
b.age++;
System.out.println("modify 메소드 내의 a의 나이: " + a.age);
System.out.println("modify 메소드 내의 a의 참조 주소: " + a.hashCode());
System.out.println("modify 메소드 내의 b의 나이: " + b.age);
System.out.println("modify 메소드 내의 b의 참조 주소: " + b.hashCode());
}
}
위와 같이 전달된 참조 변수(People b
)에 새로운 객체(new People(30);
)를 할당하면 원래 객체에 반영되지 않습니다.
testReference()
메소드 안에서 두 개의 People 객체를 가리키는 참조 변수 a와 b를 선언하고 초기화하면 아래와 같이 두 참조 변수는 각각의 객체를 가리키게 됩니다. 이때 참조 변수는 JVM의 Stack 영역에 저장되고, 객체는 Heap 영역에 저장됩니다.
- 변수의 선언과 초기화
변수는 Stack 영역에 생성되고 실제 객체는 Heap 영역에 생성되어 저장하고 있습니다. 각각의 변수는 Heap 영역에 있는 객체의 주소값을 저장하고 이를 통해 참조하고 있습니다.
- modify(People a, People b) 호출
modift() 메소드를 통해서 넘겨 받은 People 타입의 참조 변수는 객체의 메모리 주소값을 저장하고 있습니다. modify() 메소드가 호출되면 a, b와 동일 객체의 주소값을 가지는 참조 변수가 복사되어 생성됩니다.
이는 메소드의 스코프 영역이 다르기 때문입니다. 메소드 내에서 사용되는 변수는 지역 변수로써 메소드가 종료되면 그 값이 사라집니다. testReference() 메소드의 a, b 원본 참조 변수와 modify() 메소듸 a, b 복사본 참조 변수가 가리키는 객체는 현재까지 같습니다.
- modify() 메소드 실행 후, b 참조 변수에 새 객체 할당
testReference() 메소드 안의 원본 참조 변수 a와 modify() 메소드 안의 복사본 참조 변수 a가 가리키는 객체는 People_A로 같기 때문에 같은 객체를 공유하고 있습니다. 따라서 값을 변경하면 원본에도 그대로 반영됩니다.
그러나 modify() 메소드 안의 복사본 참조 변수 b에 새로운 객체를 생성하고 이를 참조하도록 하였기 때문에 testReference() 메소드 안의 원본 참조 변수 b와 modify() 메소드 안의 복사본 참조 변수 b가 가리키는 객체는 다르게 되었습니다.
그래서 PeoPle_B의 age를 30으로 바꾸어도, 원본 참조 변수 b가 가리키는 객체는 기존에 생성되어있는 객체이기 때문에 값이 변경되지 않습니다.
두 참조 변수가 참조하는 객체가 다르다는 것을 위 코드를 실행하여 해쉬코드 값으로 확인할 수 있습니다.
- testReference() 메소드가 끝나기 직전
modify() 메소드에서 사용된 원본 참조 변수의 복사본인 a, b 참조 변수는 메소드가 종료됨에 따라 해당 값이 사라지게 됩니다. 이는 두 변수가 지역 변수로 인식되기 때문입니다.
따라서 더 이상 어떤 변수도 참조하지 않는 new People(age=30) 객체는 Garbage Collector에 의해서 제거됩니다.
👉 Java에서는 참조한 객체 주소값 자체를 넘기는 것이 아니라 참조 변수에 저장된 주소값 복사하여 넘기기 때문에 기존의 Call by Reference의 개념이 적용되지 않습니다. 따라서 동일한 이름의 참조 변수라도 다른 스코프 영역에서 새롭게 선언된 객체를 참조한다면 원본 참조 변수의 주소값이 바뀌지 않습니다.
Uploaded by N2T