Call By Value
: 값에 의한 호출형태로 값을 복사하는 것
public class CallByValue {
public static void main(String[] args) {
int a = 10, b = 20; //step 1, step 2
System.out.println(plus(a, b)); // step 3
System.out.println(a); //10
}
public static int plus(int x, int y) {
x += y; //step 4
return x;
}
}
Java
복사
step 별 메모리 변화
두개의 int형 인자값을 더해서 반환하는 간단한 덧셈 정적 메서드와 이 메서드에 10, 20을 인자값으로 전달하여 반환값을 출력하고 있다.
이 때, 각각의 인자값 10과 20은 기본 타입(primitive type)으로 값은 복사되어 전달되기에 a에 b를 더해줬지만 전혀 영향을 받지 않는다. 그렇기에 plus 메소드 호출 후 다시 a를 출력해도 결과 값은 10이 나온다.
즉, Call By Value에서는 메서드 호출 시 사용되는 인자의 메모리에 저장되어 있는 값을 복사하여 보낸다.
Call By Reference
: 값이 아닌 주소(Address)를 보내면서 전달받은 곳에선 해당 주소를 참조하여 데이터에 접근한다.
public class CallByReference {
public static void main(String[] args) {
Food apple = new Food("사과", 1000);
System.out.println("apple = " + apple);
updatePriceByShelfLife(apple);
System.out.println("apple = " + apple);
}
private static void updatePriceByShelfLife(Food food) {
final Period period = food.getOverShelfLife(LocalDate.of(2021, Month.MAY, 23));
if (!period.isNegative()) {
food.changePrice(food.getPrice() - 500);
}
}
private static class Food {
private String name;
private int price;
private LocalDate shelfLife;
public Food(String name, int price) {
this.name = name;
this.price = price;
shelfLife = LocalDate.of(2021, Month.MAY, 20);
}
public String getName() {
return name;
}
public int getPrice() {
return price;
}
public Period getOverShelfLife(LocalDate targetDate){
return Period.between(shelfLife, targetDate);
}
public void changePrice(int price) {
this.price = Math.max(price, 0);
}
@Override
public String toString() {
return "[name: " + name + ", price: " + price + "]";
}
}
}
Java
복사
⇒ update 메소드 호출 전 : [name: 사과, price: 1000]
⇒ update 메소드 호출 후: [name: 사과, price: 500]
로직의 updatePriceByShelfLife() 정적 메소드를 통해 해당 음식의 유통기한을 검사해 유통기간이 지났을 경우 500원을 깎는 로직이다. 여기서 main 로직에 있는 apple과 updatePriceByShelfLife() 에 인자값으로 전달된 apple은 같은 참조주소를 바라본다.
그렇기에 apple을 매개변수로 받은 정적 메서드 updatePriceByShelfLife에서 apple의 값을 수정하면,
main로직의 apple도 동일하게 가격이 변경되있는 것이다.
그래서 Java의 참조변수는 call by value인가 call by reference 인가?
위 예제를 보고 생각을 하면 당연히 Call By Reference 라는 생각이 들 수 있지만, 이런 고민을 해 볼 필요가 있다. updatePriceByShelfLife(apple); 이 메서드를 호출하면서 apple을 넘겨줄 때 참조 주소 값을 넘겨주고 매개변수를 받은 메서드측에서도 해당 참조주소를 참조해서 객체를 참조한다.
하지만, 참조주소가 전달되는 과정은 그저 주소값을 복사해서 전달해줄 뿐이다.
다음 그림을 보자.
1.
new Food("사과", 1000) 생성자 호출로 0x0001 번지에 객체 생성
2.
생성된 객체가 있는 번지(0x0001)을 참조변수 apple의 value로 저장
3.
apple을 updatePriceByShelfLife메서드로 전달
a.
0x0003 번지를 차지한 매개변수 food에 전달받은 참조 주소(0x0001) 저장
위와 같은 과정으로 결국 메서드 호출로 인자값이 전달될 때 주소값을 값과 같이 새로운 번지에 주소값 자체를 값으로 취급해 저장해서 사용한다.
정리하자면, 참조 주소 자체를 값으로 취급해 복사해 전달한다.
그렇기에 물리적으로만 볼 때는 Call By Value에 더 가깝다고 볼 수 있다.
논리적인 부분까지 살피자면 더 고민해봐야겠지만, 우선 이정도로 정리한다.