백문이불여일타:
public class PassingValueType {
public static void main(String[] args) {
PassingValueType test = new PassingValueType();
int anInteger = 1;
System.out.println("before: " + anInteger);
test.testAssignmentOperation(anInteger);
System.out.println("after: " + anInteger);
}
void testAssignmentOperation(int anInteger) {
anInteger = 2;
}
}
결과는 다음과 같습니다:
before: 1 after: 1
반면 Basic 같은 언어는 기본적으로 Call by Reference를 사용하기 때문에 위와 같은 코드를 작성하면:
before: 1 after: 2라는 결과가 나옵니다(Basic에서 Call by Value를 사용하려면 byval 키워드를 사용합니다).
int, float, double, char 등과 같은 Value Type에 대해서는 위에서 살펴본 바와 같이 별로 헷갈리는 부분이 없습니다. 문제는 ArrayList, HashMap 등과 같은 Reference Type 들입니다:
import java.util.*;
public class PassingReferenceType {
public static void main(String[] args) {
PassingReferenceTypetest = new PassingReferenceType();
Map aMap = new HashMap();
System.out.println("before: " + aMap.size());
test.testDotOperation(anInteger);
System.out.println("after: " + aMap.size());
}
void testDotOperation(Map aMap) {
aMap.put("Key", "Value");
}
}
결과는 PassingValueType의 경우와 달리 다음과 같습니다:
before: 0 after: 1
이게 어떻게 된걸까요? HashMap과 같은 Reference Type은 Call by Reference라는 얘기일까요? 그렇지 않습니다. Reference Type 역시 Call by Value로 전달됩니다. 다음 코드를 봅시다:
import java.util.*;
public class PassingReferenceType2 {
public static void main(String[] args) {
PassingReferenceTypetest = new PassingReferenceType();
Map aMap = new HashMap();
aMap.put("key", "value");
System.out.println("before: " + aMap.size());
test.testAssignmentOperation(anInteger);
System.out.println("after: " + aMap.size());
}
void testAssignmentOperation(Map aMap) {
aMap = new HashMap();
}
이 코드의 실행 결과는 다음과 같습니다:
before: 1 after: 1
PassingReferenceType과 PassingReferenceType2의 차이가 뭐길래 이렇게 다른 결과가 나오는걸까요? 차이는 메서드 내부에서 수행된 연산이 대입 연산(Assignment operation)인지, Dot 연산(Dot operation)인지에 있습니다. PassingReferenceType.testDotOperation()에서는 전달된 레퍼런스 변수의 값 자체를 바꾸는게 아니라 레퍼런스 변수가 지칭하고 있는 인스턴스의 메서드를 호출하여(Dot operation) 인스턴스의 내용을 바꾸고 있습니다. 반면 PassingReferenceType2.testAssignmentOperation()에서는 전달된 레퍼런스 변수에 새로운 인스턴스의 레퍼런스 값을 대입(Assignment operation)하고 있습니다.
그럼 파라메터에 대한 대입 연산이 왜 메서드 외부에서는 무시되는 것일까요? 그 이유는 Call by Value의 특성 때문입니다. 변수를 Call by Value로 전달하게 되면 변수가 가지고 있던 값을 복사하여 Stack에 넣게 되고, 호출된 메서드는 이 복사된 값을 다루게 됩니다. 그리고 이 복사된 값은 메서드 실행이 끝나면 사라집니다. 따라서 대입 연산을 아무리 수행해봤자 원래의 변수가 바뀌는 것이 아니라, 메서드 내부에서만 존재하는 복사된 변수가 바뀌는 것입니다. 그럼 String과 같은 Reference Type을 전달하면 그 문자열이 몽땅 복사되니까 비효율적이지 않을까요? 그렇지 않습니다. 복사되는 것은 String 인스턴스가 아니라, String 인스턴스를 가르키고 있는 레퍼런스값이기 때문입니다.
지금까지 설명한 내용을 헷갈리지 않고 이해할 수 있는 쉬운 방법은 "전달 대상"과 "전달 방법"을 구분하는 것입니다. 즉, "무엇을" 전달하는지와 "어떻게" 전달하는지를 구분해야 한다는 것입니다. PassingValueType에서 우리가 전달한 대상은 int 값인 "1"이다. PassingReferenceType에서 전달한 대상은 HashMap 인스턴스에 대한 참조값(Reference)입니다. 전달 방법은 두 경우 모두 Call by Value입니다.
합쳐서 말해보면,
- PassingValueType에서는 int 값을(무엇을) Call by Value로(어떻게) 전달
- PassingReferenceType에서는 HashMap 인스턴스의 참조값을(무엇을) Call by Value로(어떻게) 전달
PREV
