콘텐츠로 건너뛰기

How Java Pass Arguments

프로그래밍을 하면서 항상 method를 사용하고 거기에 관련된 arguments를 넘긴다. 그런데 어떻게 다른 method에 값들이 넘어가는지 메커니즘을 정확히 알지 못하면 가끔씩 미묘한 버그가 발생하기도 한다. 오늘 할 이야기는 Java에서 어떻게 arguments를 method로 넘기는지에 대해서다. Java를 공부해봤다면 한번 쯤 들어봤을 것 같다. 그리고 그것을 공부하면서 ‘음.. 그렇구나. 이해했으니 넘어가자’ 그러고 넘어갔다. 그리고 실제로 이해했다고 생각한 개념들에 대해서 제대로 된 깨달음을 얻을 때는 그와 관련된 버그때문에 고생하다 간신히 고쳤을 때일 것 같다!

문제가 발생했던 때는 Dijkstra’s Shortest Path Algorithm을 이용해서 Flight Scheduler를 만드는 중이었다. 나에게는 Flights들이 주어지고 그것을 이용해서 승객들의 출발지 공항부터 도착지 공항까지 최단 시간의 루트를 제공해주는 프로그램이다.

/* Update fringe Edge */
for (Flight flight: flightList) {
    String destination = flight.getDest();
    Distance d = distanceMap.get(destination);
    long minutes = ...
    /* Update d, if minutes shorter than old one */
    distanceMinHeap.insert(d);
}

다음은 코드 중 일부이다. Shortest path를 가져온 뒤 Fringe Edge를 업데이트를 해주는 부분이다. 마지막으로 Edge weight를 업데이트한 뒤 Min-Heap에 집어 넣어준다. (이때 Min-Heap은 minutes을 기준으로 heapify된다.)

이렇게 Dijkstra’s Algorithm을 제대로 구현하고 동작시키면, 잘못된 경로가 나온다.

한참을 고생하다 Heap을 뜯어보았다. 그리고 문제의 실마리를 찾았다.

<현재 Heap 상태>
i=0 [src: A airport, dest: B airport, minutes=1329]
<element 추가>
Distance [src: C Airport, dest: D airport, minutes=299]
<결과>
i=0 [src: C Airport, dest: D airport, minutes=299]
i=1 [src: C Airport, dest: D airport, minutes=299] <- ????

다음 결과에서 i=1의 element를 보고 머리가 번뜩였다. 두 element가 같은 주소를 가리키고 있구나!

Distance d = distanceMap.get(destination);에서 d는 새로운 주소에 변수를 할당받는 것이 아니라 Map에 있던 Distance를 가리키고 있는 것이다.

In Java, arguments are passed by value

먼저 Pass by value가 무엇일까. Pass by value는 어떤 arguments들이 method로 전달될 때 arguments들의 복사본이 전달되는 것이다. 그렇기 때문에 method 안에서 전달된 arguments들의 값을 아무리 바꾸어도 method 바깥의 variables들의 값은 그대로다.(복사한 것들을 method 안으로 넘겼기 때문에!)

이야기를 더 진행하기 전에 Java에서 arguments들이 어떻게 memory에 저장되는지에 대해서 알아보자. 먼저 Java에는 두 가지 종류의 변수 종류가 있다: primitivesobjects이다.

Primitive variables는 항상 stack에 저장된다. 하지만 Object의 경우 두 단계를 거쳐서 저장된다. 먼저 object의 실제 데이터가 Heap 영역에 저장되고 그 reference(pointer, Heap 영역의 주소)가 stack에 저장된다.

그렇다면 Java에서는 어떤 방식으로 arguments들이 전달될까?

Java에서는 항상 arguments들은 pass by value 형식으로 전달된다. 그래서 method를 호출할 때마다 다음과 같은 과정이 반복된다.

  • argument의 복사본들이 stack에 생성되고 그것의 복사본들을 method로 넘겨준다.
    • 만약에 argument가 primitive type이었다면, 단순하게 복사본을 stack에 생성하고 그것을 method안으로 넘겨준다.
    • 만약에 argument가 object type이라면, 그 reference(pointer, Heap 영역의 주소)를 stack에 저장하고 그것을 method에게 넘겨준다. 그렇기 때문에 method를 호출하기 전 argument로 넘어가는 variable과 method를 호출하면서 method로 넘어가는 variable은 같은 object data를 가리키고 있다.

two objects pointing to same heap address

그렇기 때문에 아까 발생한 문제는 위와 같은 그림으로 나타낼 수 있다. Map에 있는 Distance object와 get으로 얻은 Distance reference는 같은 Heap 영역을 가리키고 있다. 그래서 하나의 값을 바꾸면 다른 값도 변하게 된다.

그렇다면 어떻게 문제를 해결할 수 있을까? 바로 new를 이용해서 Heap영역에 새 메모리를 할당받아서 그곳을 가리키는 reference를 이용하면 될 것이다.

/* Update fringe Edge */
for (Flight flight: flightList) {
    String destination = flight.getDest();
    Distance d = distanceMap.get(destination);
    /* Allocate Heap space */
    Distance newDistance = new Distance(d.getPath(), d.getMinutes());
    long minutes = ...
    /* Update using newDistance, if minutes shorter than old one */
    /* Insert newDistance */
    distanceMinHeap.insert(newDistance);
}

Conclusion

Java는 pass-by-value로 argument가 method에 전달된다. 또한 그 argument가 Object인 경우, 실제 data는 Heap영역에 저장한 후 address를 stack에 저장한다. 따라서 Object를 argument로 전달할 때는 data의 address를 전달하는 것이다.

Reference

  • https://www.programmergate.com/java-pass-reference-pass-value/

Leave a comment