티스토리 뷰

java

[JAVA] call by value

jjuniyo 2023. 6. 1. 12:10

call by value, call by reference라는 단어를 종종 들어봤지만, 들어만 보고 정확하게 알지 못해서 정리해봤다.
다른 사람들도 많이 정리해놓은 내용들이라 거의 비슷하긴 하지만 직접 해보면서 더 확실히 머릿속에 정리할 수 있었다.

실험의 흐름은 아래와 같다.

  1. person 객체를 생성한다.(name과 age를 멤버 변수로 갖는다.)
  2. person 객체의 age(int) 값을 파라미터로 넘겨서 값을 변경하면 변화가 있는지 확인한다.
  3. person 객체를 파라미터로 넘겨서 age 값을 변경하면 변화가 있는지 확인한다.
  4. person 객체를 파라미터로 넘겨서 새로운 person 객체를 재할당하고 변화가 있는지 확인한다.

실험에 앞서 person 클래스를 소개하겠다.
person 클래스는 멤버변수로 name과 age를 가지고 있다.

class Person {

    public String name;
    public int age;

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

    @Override
    public String toString() {
        return "[name] : " + name + " [age] : " + age;
    }
}

다음은 age값을 변경하는 메소드이다.
1번 메소드는 person객체가 넘겨준 age값에 새로운 age값을 할당하고 있고, 2번 메소드는 person 객체의 age값에 새로운 age값을 할당하고 있다.
각각 메소드를 호출하면 person객체의 age값이 어떻게 바뀔지 생각해보자.

// (1)
static void changeAge(int personAge, int changeAgeValue) {
    personAge = changeAgeValue;
}
// (2)
static void changeAge(Person person, int changeAgeValue) {
    person.age = changeAgeValue;
}
public static void main(String[] args) {
    Person person = new Person("Tom", 20);
    System.out.println("person 초기값 : " + person);

    changeAge(person.age, 27);
    System.out.println("age 를 넘겨서 변경했을 경우 : " + person.age);

    changeAge(person, 27);
    System.out.println("person 객체를 넘겨서 age 를 변경했을 경우 : " + person.age);
}

2번 메소드만 age 값에 변화가 있었다.

person 초기값 : [name] : Tom [age] : 20

age 를 넘겨서 변경했을 경우 : 20
person 객체를 넘겨서 age 를 변경했을 경우 : 27

이번에는 person 객체를 변경하는 메소드를 보겠다.
새로운 person 객체를 재할당하고 있다.
이때는 어떤 변화가 있을까?

// (1)
static void changePerson(Person person, String name, int age) {
    person = new Person(name, age);
}
public static void main(String[] args) {
    Person person = new Person("Tom", 20);
    System.out.println("person 초기값 : " + person);

    System.out.println("person 객체를 바꾸기 전 : " + person);
    changePerson(person, "Jake", 30);
    System.out.println("person 객체를 바꿀 경우 : " + person);
}

person 객체의 재할당을 해도 아무 변화가 없었다.

person 초기값 : [name] : Tom [age] : 20

person 객체를 바꾸기 전 : [name] : Tom [age] : 20
person 객체를 바꿀 경우 : [name] : Tom [age] : 20

 

앞에 실험들을 정리해 보면 person 객체의 멤버 변수를 변경하기 위해서는 객체를 parameter로 전달해야지 바꿀 수 있었다. 그리고 객체 자체를 재할당하여 바꾸는 행위는 아무 변화가 없었다.

java는 매개변수의 타입이 primitive type인 경우에는 값을 복사하고 reference type이면 instance의 주소값을 복사하여 메소드의 매개변수로 전달한다.

 

아래 그림을 보면서 설명해보겠다.

main 메소드의 person 참조 변수는 0x100이라는 주소값을 가진다. 주소 공간을 이용하면 name, age값에 접근할 수 있다.

changeAge(1번) 메소드가 호출되면 person.age값을 복사하여 스택 프레임에 personAge = 20이생성된다.

복사한 값을 변경한다고 해도 복사된 값인 personAge라는 지역 변수가 변경된 것이지 실제 person 객체의 age값에는 영향을 끼칠 수 없다.

그래서 person의 age값을 변경하고 싶으면 2번 메소드처럼 person 객체를 parameter로 전달하여 복사된 주소값을 이용해 실제 person 객체에 접근하여 age값을 변경해야 한다.

그런데 마지막 실험인 person 객체의 재할당했을 때는 왜 변화가 없을까?
우리는 분명히 person 객체를 넘겨주었고 그 주소에 새로운 객체를 만들어 할당했다.
그러면 참초 변수 person의 주소값이 새로운 객체의 주소값를 바라보고 있지 않을까? 라고 생각했다.

age같은 경우에는 복사된 값이므로 실제 person 객체에는 변경이 없다고 했다. 이것도 마찬가지다.
person 객체 주소값을 복사해서 parameter로 전달한 것이므로 주소값을 이용해서 멤버 변수에 접근하는 것은 가능하지만, 참조 변수의 주소를 바꾸는 것은 chagePerson 메소드 스택 프레임에 있는 참조 변수만 변화가 있을 뿐. 실제 호출한 영역의 참조 변수인 person에게는 영향이 없다.


아래 그림처럼 주소값을 바꾸면 changePerson메소드 안의 person이 다른 주소값을 가르키고 main 메소드 안의 person은 영향이 없다.
메소드를 호출할 때 parameter의 type에 따라 값 혹은 주소값을 복사해서 전달한다는 것을 명심하자.

 

마지막으로 정리하자면 java에서는 변수를 값으로 전달하므로 "call by value" 개념이 적용된다.
주소값을 이용하여 객체의 상태를 변경하여 "call by reference"와 비슷한 효과를 볼 수는 있다.


전체 코드

public class Main {
    public static void main(String[] args) {
        Person person = new Person("Tom", 20);
        System.out.println("person 초기값 : " + person);
        System.out.println();

        changeAge(person.age, 27);
        System.out.println("age 를 넘겨서 변경했을 경우 : " + person.age);
        changeAge(person, 27);
        System.out.println("person 객체를 넘겨서 age 를 변경했을 경우 : " + person.age);
        System.out.println();

        changeName(person.name, "Jake");
        System.out.println("name 을 넘겨서 변경했을 경우 : " + person.name);
        changeName(person, "Jake");
        System.out.println("person 객체를 넘겨서 name 을 변경했을 경우 : " + person.name);

        System.out.println();

        System.out.println("person 객체를 바꾸기 전 : " + person);
        changePerson(person, "Tomi", 30);
        System.out.println("person 객체를 바꿀 경우 : " + person);
    }

    static void changeAge(int personAge, int changeAgeValue) {
        personAge = changeAgeValue;
    }

    static void changeAge(Person person, int changeAgeValue) {
        person.age = changeAgeValue;
    }

    static void changeName(String name, String changeNameValue) {
        name = changeNameValue;
    }

    static void changeName(Person person, String changeNameValue) {
        person.name = changeNameValue;
    }

    static void changePerson(Person person, String name, int age) {
        person = new Person(name, age);
    }
}
class Person {
    public String name;
    public int age;

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

    @Override
    public String toString() {
        return "[name] : " + name + " [age] : " + age;
    }
}
출력
person 초기값 : [name] : Tom [age] : 20

age 를 넘겨서 변경했을 경우 : 20
person 객체를 넘겨서 age 를 변경했을 경우 : 27

name 을 넘겨서 변경했을 경우 : Tom
person 객체를 넘겨서 name 을 변경했을 경우 : Jake

person 객체를 바꾸기 전 : [name] : Jake [age] : 27
person 객체를 바꿀 경우 : [name] : Jake [age] : 27

'java' 카테고리의 다른 글

[JAVA] autoboxing과 unboxing  (0) 2023.05.31
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함