과정을 즐기자

자바가 Call By Reference가 아닌 이유 본문

Java

자바가 Call By Reference가 아닌 이유

320Hwany 2023. 8. 5. 13:50

프로그래밍 언어를 공부하다보면 Call By Value, Call By Reference 라는 용어가 자주 들립니다.

Call By Value는 값을 전달하는 방식이고 Call By Reference는 주소를 전달하는 방식입니다.

 

그렇다면 자바는 어떤 방식을 사용할까요?

변수를 Primitive type, Reference type으로 나눠 각각 테스트 해보겠습니다.   

Primitive type

 @Test
 @DisplayName("정수를 매개변수로 받아서 Call By Value로 동작하는 지 확인한다")
 void test1() {
     // given
     int a = 10;      - 1
     int b = 20;      - 2

     // when
     inner1(a, b);

     // then
     assertThat(a).isEqualTo(10);   - 1
     assertThat(b).isEqualTo(20);   - 2
}

public void inner1(int a, int b) {  
    a = 0;			- 3
    b = 0;			- 4	
    assertThat(a).isEqualTo(0);
    assertThat(b).isEqualTo(0);
}

위와 같은 테스트가 성공합니다. inner1 메소드 안에 있는 a, b와 inner1 메소드 밖에 있는 a, b가 서로 다릅니다.

즉 Call By Value 방식으로 값만 전달했기 때문에 서로 영향이 없는 것입니다.

스택 영역에 값이 할당되는데 그림에 번호를 붙여서 각각 부분이 어디에 해당되는 지 살펴보겠습니다.

 

Call By Value로 값을 전달합니다.

 

값을 변경해도 메소드 안에 있는 a, b만 변경되지 메소드 바깥에 있는 a, b는 변경되지 않습니다.

Reference type

@Test
@DisplayName("Reference Type도 Call By Value? 아니면 Call By Reference?")
void test2() {
    // given
    Member member = new Member("수정전 이름", 20);

    // when
    inner2(member);

    // then
    assertThat(member.getName()).isEqualTo("수정후 이름");
    assertThat(member.getAge()).isEqualTo(30);
}

static class Member {
    String name;
    int age;

    public Member(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

public void inner2(Member member) {
    member.setName("수정후 이름");
    member.setAge(30);
    assertThat(member.getName()).isEqualTo("수정후 이름");
    assertThat(member.getAge()).isEqualTo(30);
}

위와 같은 테스트가 통과합니다. 메소드 바깥에도 객체의 값이 변경되었으므로 주소를 전달하는 방식인 

Call By Reference라고 생각할 수 있습니다. 하지만 Reference Type도 Call By Value 방식입니다. 

 

메소드를 Member를 넘겨줄 때 Heap에 있는 Member를 참조하는 것이 아니라 

Stack에 주소 을 넘겨줍니다.

 

이제 메소드 안에서 각 필드의 값을 변경하면 Stack에 있는 주소 값이 가리키는 Heap 영역에 있는 객체가 영향을 받습니다.

 

위와 같이 전달 받은 객체를 수정하면 메소드 안, 메소드 밖에 있는 Member가 모두 같은 객체임을 확인했습니다.

여기서 한 가지 예시를 더 살펴보겠습니다. 

 

@Test
@DisplayName("객체를 새로 생성하면 힙 영역에 새롭게 생성됩니다")
void test3() {
    // given
    Member member = new Member("수정전 이름", 20);   - 1

    // when
    inner3(member);

    // then
    assertThat(member.getName()).isEqualTo("수정전 이름");
    assertThat(member.getAge()).isEqualTo(20);
}

public void inner3(Member member) {
    member = new Member("수정후 이름", 30);     - 2
    assertThat(member.getName()).isEqualTo("수정후 이름");
    assertThat(member.getAge()).isEqualTo(30);
}

매개변수로 member를 전달할 때까지는 이전 예시와 같이 Stack에 있는 주소 값이 메소드 바깥에 있는 member와

메소드 안에 있는 member가 같습니다. 하지만 new Member()로 새로운 객체를 할당하면서 전달받은 Stack의 주소 값이 바뀌고

힙 영역에 다른 객체가 새로 생성되는 것입니다. 

그렇다면 같은 방식으로 Reference Type인 String도 살펴보겠습니다.  

 @Test
 @DisplayName("String을 새로 할당하면 힙 영역에 새롭게 생성됩니다")
 void test4() {
    // given
    String a = "hello";

    // when
    inner4(a);

    // then
    assertThat(a).isEqualTo("hello");
}

public void inner4(String a) {
    a = "hi";
    assertThat(a).isEqualTo("hi");
}

 

정리

지금까지 학습 테스트를 만들어보면서 자바에서 Primitive Type, Reference Type이 Call By Value로 값을 전달한다는 것을 

확인해보았습니다. Primitive Type 같은 경우 자연스럽게 받아들여지지만 Reference Type이 왜 Call By Value인지는 

헷갈릴 수 있습니다. 정리하자면 Reference Type이라도 Stack에 값을 전달하고 그 값을 이용해서 Heap 영역을 참조합니다.

 

참고한 자료

 

[Java] Call by Value, Call by Reference

프로그래밍 언어들의 메소드 매개변수 호출 방식에는 여러 가지가 있으며 호출 방식은언어마다 다르게 되어 있습니다. 그 중 자바의 Call by Value 방식에 대해 알아봅시다 !

velog.io

 

'Java' 카테고리의 다른 글

자바 쓰레드 관리의 발전  (2) 2023.08.18
자바 쓰레드 관리의 시작  (2) 2023.08.18
enum class로 상수를 관리해야하는 이유  (0) 2023.06.22
Equals와 HashCode 비교  (0) 2022.12.13
추상 클래스와 인터페이스  (0) 2022.11.10