자바에서 call by value, call by reference 이해하기

반응형
728x90
반응형

Call by Value

값에 의한 호출
public class Main {
    public static void main(String[] args) {
        int a = 1;
        int b = 2;

        System.out.println(a); // 1
        System.out.println(b); // 2
        
        Main main = new Main();
        main.update(a);

        System.out.println(a); // 1
        System.out.println(b); // 2

    }

    void update(int a) {
        /* 지역변수 처럼 */
        a = 5;

        System.out.println(a); // 5
    }
}

 

변수 a, b에 각각 1, 2를 할당했다. 그리고 변수를 출력해보면 a = 1, b = 1로 출력된다. 그리고 update 함수를 호출하여 매개변수로 넘긴 a의 값을 5로 변경했다. update 메소드 안에서의 a 변수를 출력해보면 변경된 값인 5가 출력된다. 하지만 여기서 눈여겨볼 점은, update 메소드 호출 이후 돌아온 main 함수 내에서 호출된 변수 a의 값은 update 메소드를 호출하기 전인 1의 값으로 출력되고있다.

 

"update 메소드에서 a 값을 5로 변경했는데 a의 값은 아직도 1이다."

 

원시타입의 변수인 a, b는 매개변수로 전달된 후 해당 메소드 안에서 그 메소드의 지역변수로 정의된다. update 메서드에서는 인자의 값을 복사해서 받았기 때문이다.

 

 

Call by Reference

참조에 의한 호출
import dto.SampleDto;

public class MainTwo {
    public static void main(String[] args) {
        SampleDto sampleDto = new SampleDto();

        sampleDto.setIdx(0);
        sampleDto.setGender("F");
        sampleDto.setName("seohae");

        System.out.println(sampleDto.getGender()); // F
        System.out.println(sampleDto.getName());  // seohae

        MainTwo mainTwo = new MainTwo();
        mainTwo.update(sampleDto);

        System.out.println(sampleDto.getGender()); // M
        System.out.println(sampleDto.getName()); // juhan
    }

    void update(SampleDto sampleDto) {
        sampleDto.setGender("M");
        sampleDto.setName("juhan");
    }
}

 

SampleDto 클래스의 인스턴스 sampleDto를 생성했다. 그리고 set 메소드를 사용하여 각 필드에 값을 매핑해주었다. 출력해보면 내가 매핑한 값인 Gende : F, Name : seohae가 출력된다. 그리고 또다시 update 메소드에 sampleDto 객체를 매개변수로 호출하였다. update 메소드 안에서 매개변수로 받은 sampleDto 객체의 set 메소드를 호출하여 각 필드들의 값을 변경해주고있다. update 메소드 호출 이후 main 메소드로 돌아와 다시 필드들을 출력해보면 update 메소드에서 변경한 값으로 Gender : M, name : Juhan 출력되고있다.

 

"update 메소드에서 변경된 값으로 출력되고있다."

 

mian 메소드 안에서 인스턴스가 생성되면서 sampleDto가 가리키고있는 힙 영역의 객체가 있을 것이다.  sampleDto 객체가 update 메소드에 매개변수로 넘겨지면서, 매개변수의 객체가 sampleDto가 가리키고있는 힙 영역의 객체를 가리키게 된다.

 

import dto.SampleDto;

public class MainThree {
    public static void main(String[] args) {
        SampleDto sampleDto = new SampleDto();

        sampleDto.setIdx(0);
        sampleDto.setGender("F");
        sampleDto.setName("seohae");

        System.out.println(sampleDto.getGender()); // F
        System.out.println(sampleDto.getName());  // seohae

        MainThree mainTwo = new MainThree();
        mainTwo.updateNew(sampleDto);

        System.out.println(sampleDto.getGender()); // F
        System.out.println(sampleDto.getName()); // seohae
    }

    void updateNew(SampleDto sampleDto) {
        sampleDto = new SampleDto();
        sampleDto.setGender("X");
        sampleDto.setName("test");
    }
}

 

위 예제가 이해가 잘 되었다면 해당 예제는 바로 이해가 될 것이다. SampleDto 클래스의 인스턴스 sampleDto를 생성하였고 위 예제와 똑같이 set 메소드를 호출하여 필드들의 값을 매핑해주었다. 그리고 sampleDto 인스턴스를 매개변수로, updateNew 메소드를 호출하였다. updateNew 메소드 내부 로직을 보면 매개변수로 받아온 sampleDto 객체에 new 연산자를 사용하여 기존에 가리키던 힙 영역의 객체가 변경된다. 따라서 해당 객체는 더이상 main 에서 매개변수로 받아온 sampleDto와 같은 힙 객체를 가르키고있지 않다. 그래서 updateNew에서 set 메소드를 호출하여 값을 매핑한 결과는 updateNew 메서드를 호출한 이후에도 main 메서드에서는 updateNew 메서드 호출 이전의 값이 출력된다.

 

 

Java의 Call By Reference

위 예제에서 "sampleDto 객체가 update 메소드에 매개변수로 넘겨지면서, 매개변수의 객체가 sampleDto가 가리키고있는 힙 영역의 객체를 가리키게 된다." 이부분이 있었다. 이 부분은 아래 그림으로 이해해보자.

 

이해하기 위해서 main 메소드에서의 SampleDto 클래스의 인스턴스를 sampleDto라고 하고, update 메소드로 전달된 매개변수 sampleDto를 paramDto라고 하자.

 

Stack area Heap area
sampleDto gender = F
name = seohae
paramDto (=sampleDto 객체의 주소값을 복사하여 새로 생성)

 

위 그림에서 보듯이 sampleDto 가 가르키고있는 Heap area 안의 객체를 paramDto도 가르키고있다. 그렇기 때문에 update 메소드에서 setGender("M"), setName("juhan")을 실행했을때 Heap area 안의 객체가 변경되기 때문에 같은 객체를 바라보고있던 main 메소드의 sampleDto의 값도 변경되는 것이다. 

 

sampleDto 객체가 update 메소드에 매개변수로 전달되면서 자신의 주소값을 복사한 paramDto 를 생성하고 해당 paramDto가 sampleDto가 가르키고있던 Heap area의 객체를 가르키게되는 것이다. 몇몇 의견 중에서는 이 또한 주소값을 복사하기 때문에 Call by value가 발생한 것이라고 보고있다.

 

  • 예제
import dto.SampleDto;

public class MainFour {
    public static void main(String[] args) {
        SampleDto sampleDto1 = new SampleDto();
        SampleDto sampleDto2 = new SampleDto();

        sampleDto1.setIdx(0);
        sampleDto1.setGender("F");
        sampleDto1.setName("seohae");

        sampleDto2.setIdx(1);
        sampleDto2.setGender("M");
        sampleDto2.setName("juhan");

        System.out.println(sampleDto1.getGender()); // F
        System.out.println(sampleDto1.getName());  // seohae

        System.out.println(sampleDto2.getGender()); // M
        System.out.println(sampleDto2.getName()); // juhan

        MainFour main = new MainFour();
        main.updateNew(sampleDto1, sampleDto2);

        System.out.println(sampleDto1.getGender()); // F
        System.out.println(sampleDto1.getName());  // seohae

        System.out.println(sampleDto2.getGender()); // M
        System.out.println(sampleDto2.getName()); // juhan
    }

    void updateNew(SampleDto sampleDtoA, SampleDto sampleDtoB) {
        sampleDtoB = sampleDtoA;

        System.out.println(sampleDtoB.getGender()); // F
        System.out.println(sampleDtoB.getName());  // seohae
    }
}

 

main 메소드에서 생성된 sampleDto1, sampleDto2의 인스턴스들이 updateNew 메소드의 매개변수로 넘겨질 경우에 아래와 같은 상태가 된다.

 

Stack area Heap area
sampleDto1 gender = F
name = seohae
sampleDtoA (=sampleDto1 객체의 주소값을 복사하여 새로 생성)
sampleDto2 gender = M
name = juhan
sampleDtoB (=sampleDto2 객체의 주소값을 복사하여 새로 생성)

 

updateNew 메서드 로직 중에 아래 한줄을 보자.

sampleDtoB = sampleDtoA;

 

위 한줄이 실행된 이후의 상태는 아래와 같다.

 

Stack area Heap area
sampleDto1 gender = F
name = seohae
sampleDtoA (=sampleDto1 객체의 주소값을 복사하여 새로 생성)
sampleDtoB (=sampleDto2 객체의 주소값을 복사하여 새로 생성)
sampleDto2 gender = M
name = juhan

 

sampleDtoB 객체도 sampleDtoA가 가르키고 있는 Heap area 안의 객체를 가르키게되었다. 따라서 updateNew 메소드 안에서 호출한 sampleDtoB 의 필드들의 값들과 main 메소드에서 호출한 sampleDto2 의 필드들의 값들이 같지 않다는 결론을 이해할 수 있다. 해당 개념은 한번에 이해하기엔 매우 어렵고 헷갈리기 때문에 계속해서 보고 정리할 필요가 있다고 생각한다.

 

 

 

 

 

반응형

Designed by JB FACTORY