개발을 하다 보면 함수의 파라미터로 변수를 넘겨주어야 한다.
각 언어마다 변수를 넘겨주는 방법(Pass By Value, Pass By Reference)이 다른데, 이를 정확히 인지하지 못하면 예상치 못한 버그를 발생시킬 수 있다.
이번에는 Java가 어떠한 방식으로 파라미터를 전달하는지 살펴보자.
0. 문제 풀어보기
Pass By Value와 Pass By Reference에 대해 이야기하기 전에, 우리는 이들에 대해 얼마나 이해하고 있는지 확인하기 위해 아래의 간단한 시스템의 출력 결과를 예상해보도록 하자.
해당 유형의 문제는 실제 유명한 기업들에서 자주 출제되는 문제이기도 하다.
0-1. 실행할 프로그램
class Dog {
private String name;
public Dog (String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName (String name) {
this.name = name;
}
}
public class Test {
public static void main(String[] args) {
int x = 10;
int[] y = {2, 3, 4};
Dog dog1 = new Dog("강아지1");
Dog dog2 = new Dog("강아지2");
// 함수 실행
foo(x, array, dog1, dog2);
// 어떤 결과가 출력될 것 같은지 혹은 값이 어떻게 변할지 예상해보세요!
System.out.println("x = " + x);
System.out.println("y = " + y[0]);
System.out.println("dog1.name = " + dog1.getName());
System.out.println("dog2.name = " + dog2.getName());
}
public static void foo(int x, int[] y, Dog dog1, Dog dog2) {
x++;
y[0]++;
dog1 = new Dog("이름 바뀐 강아지1");
dog2.setName("이름 바뀐 강아지2");
}
}
위와 같은 프로그램을 실행하게 되면 각각의 값들은 어떻게 변하겠는가?
충분히 고민한 후에 답을 확인해보도록 하자.
0-2. 실행 결과
위를 실행하면 아래와 같은 결과가 나온다.
x = 10
y = 3
dog1.name = 강아지1
dog2.name = 이름 바뀐 강아지2
본인이 예상했던 결과와는 다른 결과가 나왔을 수 있다.
그러한 근본적인 이유는 Java가 Pass By Value로 변수를 전달하기 때문이다.
왜 이러한 결과가 나왔는지 이해하기 위해 Java의 작동 방식을 자세히 알아보도록 하자.
1. 메모리 할당에 대한 이해
어떠한 변수를 선언한다는 것은 메모리를 할당한다는 것을 의미한다.
변수를 선언하기 위해 할당되는 메모리로는 크게 스택과 힙이 있다.
스택 영역 | 함수의 호출과 함께 지역변수 또는 매개변수 등이 할당되며, 정렬된 방식으로 메모리가 할당되고 해제됨. |
힙 영역 | 클래스 변수 (or 인스턴스 변수) 또는 객체 등이 할당되며, 우연하고 무질서하게 메모리가 할당됨. → 그래서 JVM 은 무질서하게 관리되는 힙 영역을 위주로 가비지 컬렉터를 통해 메모리 해제를 관리 |
이러한 기초 지식을 바탕으로 지역에 존재하는 원시 변수와 객체의 메모리 할당을 먼저 살펴보도록 하자.
(인스턴스 변수로 존재하는 원시변수는 힙 영역에서 관리됨.)
(아래의 설명에서는 지역에 존재하는 원시 변수인 Local Primitive Value를 기준으로 설명하고 있음.)
1-1. 원시 변수(Primitive Value)의 메모리 할당
Java에서 변수는 객체가 아닌 실제 값들인 int, double, float boolean 등과 같은 원시 값(Primitive Value)들이 존재한다.
예를 들어 다음과 같이 함수 내에서 지역 변수로 원시 변수들을 선언하였다고 가정하자.
public void test() {
int x = 3;
float y = 10.012f;
boolean isTrue = true;
}
메모리에서는 Stack 영역에 실제 값들이 할당된다.

1-2. 객체(Object)의 메모리 할당
객체는 원시변수와 다르게 값이 할당된다.
예를 들어 다음과 같은 String 객체를 추가로 생성하였다고 하자.
public void test() {
int x = 3;
float y = 101.012f;
boolean isTrue = true;
String name = "MangKyu";
}
메모리에 값은 다음과 같이 할당된다.
먼저 객체의 경우 Heap 영역에 실제 값이 할당된다.
그리고 이에 접근하기 위해 Stack 영역에는 Heap 영역에 존재하는 값의 실제 주소가 저장된다.
→ C/C++에서는 이를 포인터(pointer)라고 부름
즉, Stack 영역에는 실제 값이 존재하는 Heap 영역의 주소가 저장된다.

여기에 더해 다음과 같이 크기가 3인 String 배열을 추가적으로 선언하였다고 하자.
public void test() {
int x = 3;
float y = 101.012f;
boolean isTrue = true;
String name = "MangKyu";
String[] names = new String[3];
names[0] = "I";
names[1] = "am";
names[2] = "MangKyu";
}
배열도 객체이기 때문에 다음과 같이 메모리가 할당되게 된다.

여기서 주목해야 할 것은 크게 세가지가 있다.
1. Heap 영역은 Stack 영역과 다르게 무질서하게 메모리 공간을 활용한다.
2. 객체의 메모리 할당인 경우 Stack 영역에 실제 값을 참조하기 위한 Reference(참조값)이 저장되고
이를 통해 참조하여 실제 값에 접근한다.
3. 배열의 경우 또 그 배열의 인덱스마다 참조값이 할당되며 이를 통해 접근한다.
2. 문제 파헤쳐보며 Pass By Value 이해하기
출처: https://mangkyu.tistory.com/105 [MangKyu's Diary]
출처: https://mangkyu.tistory.com/106 [MangKyu's Diary]
개발을 하다 보면 함수의 파라미터로 변수를 넘겨주어야 한다.
각 언어마다 변수를 넘겨주는 방법(Pass By Value, Pass By Reference)이 다른데, 이를 정확히 인지하지 못하면 예상치 못한 버그를 발생시킬 수 있다.
이번에는 Java가 어떠한 방식으로 파라미터를 전달하는지 살펴보자.
0. 문제 풀어보기
Pass By Value와 Pass By Reference에 대해 이야기하기 전에, 우리는 이들에 대해 얼마나 이해하고 있는지 확인하기 위해 아래의 간단한 시스템의 출력 결과를 예상해보도록 하자.
해당 유형의 문제는 실제 유명한 기업들에서 자주 출제되는 문제이기도 하다.
0-1. 실행할 프로그램
class Dog { private String name; public Dog (String name) { this.name = name; } public String getName() { return name; } public void setName (String name) { this.name = name; } } public class Test { public static void main(String[] args) { int x = 10; int[] y = {2, 3, 4}; Dog dog1 = new Dog("강아지1"); Dog dog2 = new Dog("강아지2"); // 함수 실행 foo(x, array, dog1, dog2); // 어떤 결과가 출력될 것 같은지 혹은 값이 어떻게 변할지 예상해보세요! System.out.println("x = " + x); System.out.println("y = " + y[0]); System.out.println("dog1.name = " + dog1.getName()); System.out.println("dog2.name = " + dog2.getName()); } public static void foo(int x, int[] y, Dog dog1, Dog dog2) { x++; y[0]++; dog1 = new Dog("이름 바뀐 강아지1"); dog2.setName("이름 바뀐 강아지2"); } }
위와 같은 프로그램을 실행하게 되면 각각의 값들은 어떻게 변하겠는가?
충분히 고민한 후에 답을 확인해보도록 하자.
0-2. 실행 결과
위를 실행하면 아래와 같은 결과가 나온다.
x = 10 y = 3 dog1.name = 강아지1 dog2.name = 이름 바뀐 강아지2
본인이 예상했던 결과와는 다른 결과가 나왔을 수 있다.
그러한 근본적인 이유는 Java가 Pass By Value로 변수를 전달하기 때문이다.
왜 이러한 결과가 나왔는지 이해하기 위해 Java의 작동 방식을 자세히 알아보도록 하자.
1. 메모리 할당에 대한 이해
어떠한 변수를 선언한다는 것은 메모리를 할당한다는 것을 의미한다.
변수를 선언하기 위해 할당되는 메모리로는 크게 스택과 힙이 있다.
스택 영역 | 함수의 호출과 함께 지역변수 또는 매개변수 등이 할당되며, 정렬된 방식으로 메모리가 할당되고 해제됨. |
힙 영역 | 클래스 변수 (or 인스턴스 변수) 또는 객체 등이 할당되며, 우연하고 무질서하게 메모리가 할당됨. → 그래서 JVM 은 무질서하게 관리되는 힙 영역을 위주로 가비지 컬렉터를 통해 메모리 해제를 관리 |
이러한 기초 지식을 바탕으로 지역에 존재하는 원시 변수와 객체의 메모리 할당을 먼저 살펴보도록 하자.
(인스턴스 변수로 존재하는 원시변수는 힙 영역에서 관리됨.)
(아래의 설명에서는 지역에 존재하는 원시 변수인 Local Primitive Value를 기준으로 설명하고 있음.)
1-1. 원시 변수(Primitive Value)의 메모리 할당
Java에서 변수는 객체가 아닌 실제 값들인 int, double, float boolean 등과 같은 원시 값(Primitive Value)들이 존재한다.
예를 들어 다음과 같이 함수 내에서 지역 변수로 원시 변수들을 선언하였다고 가정하자.
public void test() { int x = 3; float y = 10.012f; boolean isTrue = true; }
메모리에서는 Stack 영역에 실제 값들이 할당된다.

1-2. 객체(Object)의 메모리 할당
객체는 원시변수와 다르게 값이 할당된다.
예를 들어 다음과 같은 String 객체를 추가로 생성하였다고 하자.
public void test() { int x = 3; float y = 101.012f; boolean isTrue = true; String name = "MangKyu"; }
메모리에 값은 다음과 같이 할당된다.
먼저 객체의 경우 Heap 영역에 실제 값이 할당된다.
그리고 이에 접근하기 위해 Stack 영역에는 Heap 영역에 존재하는 값의 실제 주소가 저장된다.
→ C/C++에서는 이를 포인터(pointer)라고 부름
즉, Stack 영역에는 실제 값이 존재하는 Heap 영역의 주소가 저장된다.

여기에 더해 다음과 같이 크기가 3인 String 배열을 추가적으로 선언하였다고 하자.
public void test() { int x = 3; float y = 101.012f; boolean isTrue = true; String name = "MangKyu"; String[] names = new String[3]; names[0] = "I"; names[1] = "am"; names[2] = "MangKyu"; }
배열도 객체이기 때문에 다음과 같이 메모리가 할당되게 된다.

여기서 주목해야 할 것은 크게 세가지가 있다.
1. Heap 영역은 Stack 영역과 다르게 무질서하게 메모리 공간을 활용한다.
2. 객체의 메모리 할당인 경우 Stack 영역에 실제 값을 참조하기 위한 Reference(참조값)이 저장되고
이를 통해 참조하여 실제 값에 접근한다.
3. 배열의 경우 또 그 배열의 인덱스마다 참조값이 할당되며 이를 통해 접근한다.
2. 문제 파헤쳐보며 Pass By Value 이해하기
출처: https://mangkyu.tistory.com/105 [MangKyu's Diary]
출처: https://mangkyu.tistory.com/106 [MangKyu's Diary]