본문 바로가기

JAVA

Java 에는 Call by reference 가 없다.

1. Call by Value? Call by Reference?

 

프로그래밍 언어들의 메소드 매개변수 호출방식에는 여러가지가 있으며 호출 방식은 언어마다 다르게 설정되어 있다.

 

대표적으로 C/C++ 는 Call by Reference를 사용한다.

 

Call by Value 는 함수의 인자를 전달할때 '값을 전달하는 방식' 이고

Call by Reference 는 '주소를 전달하는 방식' 이다. 

 

자바는 확실하게 Call by Value 방식을 사용한다.

 

2. Java 에 Call by Reference 가 있을까?

 

결론부터 말하자면 Call by Reference 를 지원하지 않는다. 

 

다음의 코드를 살펴보자

 

import org.assertj.core.api.Assertions;
import org.junit.Test;

public class TestSample {

    @Test
    public void Call_by_Reference_체크(){
        //given
        SampleDto dto1 = new SampleDto(1);

        //when
        this.setData(dto1);

        //then
        Assertions.assertThat(dto1.data).isEqualTo(2);
    }


    private void setData(SampleDto dto1arg){
        dto1arg.data=2;
    }

    public static class SampleDto{
        private int data;

        public SampleDto(int data) {
            this.data = data;
        }
    }
}

 

setData 메소드는 SampleDto 형 매개변수를 받아 해당 객체의 data 의 값을 2로 바꾸는 메소드이다.

 

Test 코드를 실행시키면 문제없이 실행되는것을 확인할 수 있다. 

 

이 코드를 보면 Java 가 Call by Reference 로 동작하는것 처럼 보일 수 있지만 사실은 메소드가 수행될때 Java의 메모리 구조는 다음과 같다.

setData 메소드 수행시 동작하는 JVM 메모리 구조

 

new 연산자로 객체를 생성하면 Stack 영역에 해당 객체의 변수가 저장되고 Heap 영역에 객체를 바라보게 된다.

 

setData 메소드가 수행되면서 매개변수인 dto1arg 가 Stack 영역에 저장되고 같은 dto1과 같은곳을 바라보게 되며 해당 객체의 멤버변수의 값을 변경하는것이 가능하다.

 

하지만 dto1 에서 dto1arg로 매개변수를 넘기는 과정에서 직접적인 참조를 넘긴것이 아니라 주소값을 복사해서 넘기기 때문에 이는 Call by Value 이다. 복사된 주소값으로 참조가 가능하기 때문에 객체의 내용이 변경되는 것이다.

 

자바의 메모리 구조에 대한 구체적인 설명은 다음의 포스트에서 확인이 가능하다.

JVM의 구조와 자바의 메모리 구조

 

JVM의 구조와 자바의 메모리 구조

자바 애플리케이션이 실행될 때 JVM에서 일어나는 일, 과정은 어떻게 될까? 우리가 자바 애플리케이션을 실행할 때 JAVA와 OS 사이에서 동작하는 JVM(Java Virtual Machine)이라는 소프트웨어로 구현한 머

dev-cool.tistory.com

 

조금더 정확하게 확인하기 위해서 위의 코드를 다음과 같이 수정해보자.

 

import org.assertj.core.api.Assertions;
import org.junit.Test;

public class TestSample {

    @Test
    public void Call_by_Reference_체크(){
        //given
        SampleDto dto1 = new SampleDto(1);
        SampleDto dto2 = new SampleDto(1);

        //when
        this.setData(dto1,dto2);

        //then
        Assertions.assertThat(dto1.data).isEqualTo(2);
        Assertions.assertThat(dto2.data).isEqualTo(2);
    }


    private void setData(SampleDto dto1arg,SampleDto dto2arg){
        dto1arg.data=2;
        dto2arg = dto1arg;
    }

    public static class SampleDto{
        private int data;

        public SampleDto(int data) {
            this.data = data;
        }
    }
}

 

실행 결과

이번에는 setData 메소드에 SampleDto 형 매개변수를 2개를 받아와 dto1arg 객체의 data를 2 로 변경하고, dto1arg의 주소를 data2arg 변수로 덮어쓰기를 한다.

 

Call by Reference 방식을 지원한다면 dto2arg 객체의 data 값은 2로 바뀌어야 하는데 실행결과를 보면 실제 data 값은 1로 출력되는것을 확인할 수 있다.

 

단계별로 상황을 생각해 보면 Test 코드가 실행될때 SampleDto 형 객체를 new 연산자로 2번 생성한다.

 

Test 코드가 실행되고 new연산을 한 직후

이제 setData 메소드가 수행되면서 매개변수로 입력되는 값에 의한 변수가 할당되며 각각의 객체를 바라보게 된다.

 

setData 메소드 실행 직후

 

dto1arg.data = 2; 구문을 수행하면서 다음의 형태가 되고

 

dto1arg.data = 2; 수행 이후

 

dto2arg = dto1arg; 구문을 수행하면 

 

dto2arg = dto1arg; 수행 이후

 

dto2arg는 dto1arg의 참조를 바라보게 된다.

 

즉, 기존에 dto2arg가 바라보고 있던 객체의 값이 수정되는 것이 아니라 dto1arg가 바라보는곳을 바라보게 되는것이므로, dto2 가 바라보고 있는 객체의 값이 변경되는 것이 아니기 때문에 우리가 예상했던 결과가 나오지 않는것을 확인할 수 있다.

 

메소드 수행이 끝나게 되면 매개변수 dto2arg, dto2arg 는 Stack 메모리에서 할당 해제되며 원본인 dto2의 값은 바뀌지 않는 것을 알 수 있다.

(즉, 메소드 내부에서 dto2arg.value를 출력하면 당연히 data는 2로 출력되지만 메소드 사용이 종료되면 원본 객체의dto2의 data 는 1로 바뀌지 않는것이다.)

 

따라서 JAVA는 기본적으로 Call by Value 방식으로 값을 전달하는 것이다.