[Java] Java는 "참조에 의한 전달" 일까? "값에 의한 전달" 일까?
함수를 호출함에 있어 전달할 변수가 "값에 의한 호출(Call by Value)"인지 "참조에 의한 호출(Call by Reference)"인지는 프로그래머에게 있어 매우 중요한 이슈이며 또한 명확히 구분되어져야 한다.
"값에 의한 호출"은 변수의 값이 함수/메서드에 전달된다는 의미이며, "참조에 의한 호출"은 해당 변수에 대한 참조가 함수에 전달된다는 의미이다. 두 방법의 중요한 차이는 참조에 의한 호출이 호출할 때 전달한 변수의 내용을 직접 변경할 수 있다는 것이다.
자바는 항상 "참조에 의한 호출"이 아닌 "값에 의한 호출"을 이용해 함수에 변수의 내용을 전달한다. 그렇기에 심지어 그 변수가 객체를 포함하고 있다해도 "값으로 전달되는 참조에 의한 호출" 방식을 이용한다.
[예제 1]
public static void main(String[] args) {
Dog aDog = new Dog("Max");
Dog oldDog = aDog;
System.out.println("aDog before foo() : " + aDog.getName());
System.out.println("oldDog before foo() : " + oldDog.getName());
foo(aDog);
System.out.println("aDog after foo() : " + aDog.getName());
System.out.println("oldDog after foo() : " + oldDog.getName());
}
public static void foo(Dog d) {
System.out.println("d before new Fifi : " + d.getName());
d = new Dog("Fifi");
System.out.println("d after new Fifi : " + d.getName());
}
public static class Dog {
String name;
public Dog(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
* Result *
aDog before foo() : Max
oldDog before foo() : Max
d before new Fifi : Max
d after new Fifi : Fifi
aDog after foo() : Max
oldDog after foo() : Max
참고: https://www.javadude.com/
foo()에서 "Fifi"로 변경한 aDog은 main에서 "Max"를 그대로 유지하며 변하지 않았다. 만약 참조에 의한 호출이었다면 aDog.getName()은 "Fifi"로 변경되었을 것이다.
[예제 2]
public static void main(String[] args) {
Dog aDog = new Dog("Max");
Dog oldDog = aDog;
System.out.println("aDog before foo() : " + aDog.getName());
System.out.println("oldDog before foo() : " + oldDog.getName());
foo(aDog);
System.out.println("aDog after foo() : " + aDog.getName());
System.out.println("oldDog after foo() : " + oldDog.getName());
}
public static void foo(Dog d) {
System.out.println("d before set() : " + d.getName());
d.setName("Fifi");
System.out.println("d after set() : " + d.getName());
}
* Result *
aDog before foo() : Max
oldDog before foo() : Max
d before set() : Max
d after set() : Fifi
aDog after foo() : Fifi
oldDog after foo() : Fifi
foo()에서 최초 "Max"였으나 d를 직접 setName()으로 "Fifi"로 변경하면, 같은 참조를 하고 있던 main()의 aDog, oldDog도 모두 "Fifi"로 변경됨을 확인할 수 있다. 이는 최초 "Max"로 생성된 주소가 aDog oldDog, foo()의 d에 모두 동일하게 참조되고 있으며, foo()에서도 d를 쫒은 후 set함수를 이용해 "Fifi"로 변경하였기에, 이 모든 변수들이 "Fifi"로 변경되는 것이다.
이해를 돕기 위해 아래 간단한 코드를 살펴보자.
- Dog myDog = new Dog("Rover");
- foo(myDog);
- System.out.println(myDog.getName());
- public void foo(Dog aDog) {
- aDog.setName("Max");
- aDog = new Dog("Fifi");
- aDog.setName("Rowlf");
- }
1에서 new Dog이 생성되면서 주소 42가 myDog에 할당 되었다고 가정한다면,
4에서 aDog 변수는 42를 전달받게 되고,
5에서도 42에 있는 aDog의 Name을 "Max"로 설정할 것이다.
6에서 new Dog이 새로 생성되면서 이때 주소는 42가 아닌 다른 주소, 예를 들어 74가 aDog에 할당될 것이다.
7에서 aDog을 "Rowlf"로 설정했지만, 이젠 42의 aDog이 아닌 74의 aDog에 설정됨
3에서 foo()에서 돌아온 후, myDog을 출력한다면, 3의 myDog은 여전히 42를 가리키고 있기에 "Rover"가 출력될 것임.
자바의 객체 변수에 할당된 주소를 값으로 전달하는 참조에 의한 호출 방식이 복잡하게 보일지 몰라도 C언어의 그것 보다는 매우 단순, 명확해진 개념이라 생각된다.