Java

Java Parameter Passing Mechanism

Tommy__Kim 2023. 4. 26. 11:27

Definition

함수에 인자를 넘기는 방식에는 다음과 같은 방식이 있습니다. 

  • value
  • reference
  • result 
  • value-result
  • name

이 중 Pass By Value, Pass By reference 메서드가 널리 사용되고 있습니다. 
프로그래밍 언어들 마다 각자의 방법으로 인자들을 넘기는 방법을 택하고 있습니다.
Java의 경우 엄격하게 Pass By Value를 지키고 있습니다.

들어가기 앞서 Wikipedia에 정의된 Call By Referecne, Call By Value의 정의를 한번 짚고 넘어가겠습니다.

Call By Value

Call By Value는 method에서 변수를 사용 시 새 메모리 영역에 복사해 method의 변수에 바인딩합니다.
메서드에서 값을 변경하면 method내의 변수 값만 변하고 원래의 변수는 영향을 받지 않습니다.

Call By Reference

Call By Reference (혹은 Pass By Refrecne)는 method에서 변수를 사용 시 복사값이 아닌
변수의 참조값 (implicit reference)을 바인딩합니다.
메서드에서 값을 변경하면 원래의 변수의 값도 영향을 받습니다.

Parameter Passing In Java

Java에서 Primitive Variable과 Non-Primitive는 Stack 메모리에 다르게 저장됩니다. 

  • Primitive Variable
    • 실제 값 자체를 Stack 메모리에 저장 
  • Non-Primitive Variable
    • 객체를 가리키고 있는 주소인 참조 변수(Reference Variable)를 Stack 메모리에 저장 

Java에서의 변수들은 항상 Call By Value입니다.

메서드 호출이 일어나면,  그 변수가 Value(값)이건 Reference(참조)이건 간에 변수를 복사해 Stack 메모리에 생성한 후 메서드로 전달됩니다. 

  • Primitive Variable 
    • 단순하게 값을 Stack 메모리에 복사한 후 메서드로 전달
  • Non-Primitive Variable
    • 실제 객체를 가리키고 있는 참조 값을 복사한 후 메서드로 전달 (실제 객체는 Heap에 저장됩니다.)

 

이쯤 되면 Non-Primitive Variable은 Call By Reference 아닌가?
라는 생각이 드실 거 같은데요.

(저 또한 그랬었습니다)
예제와 함께 조금 더 알아보도록 하겠습니다.

Passing Primitive Type

Primitive 변수들은 Stack 메모리에 직접적으로 저장됩니다. 

Primitive 변수들이 변수로써 넘겨질 때마다, 실제 파라미터가 복사되어 스택 메모리에 생성됩니다. 

이렇게 생성된 변수들의 생명주기는 메서드가 실행되는 동안입니다. 그 후에는 Stack 메모리에서 지워지고 버려집니다.

 

[Code]

public class Primitive {

    public static void main(String[] args) {
        int x = 5;
        int y = 10;
        print("[before]",x, y);
        swap(x, y);
        print("[after]",x, y);
    }

    private static void swap(int newX, int newY) {
        int temp = newY;
        newY = newX;
        newX = temp;
        print("[swap]", newX, newY);
    }

    private static void print(String status, int x, int y) {
        System.out.println(status + " x: " + x + ", y : " + y);
    }
}

[실행 결과]

[before] x: 5, y : 10
[swap] x: 10, y : 5
[after] x: 5, y : 10

실행 결과를 확인해 보면 method swap에서는 값이 바뀌었지만 원래의 값은 바뀌지 않은 것을 확인할 수 있습니다.

 

[동작 원리]

1. main() 메서드가 실행될 때 x, y의 변수는 primitive type이므로 그 값이 바로 Stack 메모리에 저장됩니다.

2. swap() 메서드를 호출했을 때, Stack memory의 다른 위치에 똑같은 값이 복사되어 저장됩니다. 

3. swap()에서의 값의 수정은 오로지 복사된 값에만 영향을 미치고 원래의 값에는 영향을 미치지 않습니다. 

 

Passing Object References

Java에서 모든 객체는 Heap 영역에 동적으로 저장됩니다. 이러한 객체들은 Reference Variable(참조변수)를 통해 참조됩니다.

Java 객체는 Primitive와 다르게 두 단계로 저장됩니다.

Reference Variabale(참조변수)는 Stack 메모리 영역에 저장되며, Reference Variable이 가리키는

실제 객체는 Heap 영역에 저장됩니다. 

객체가 변수로 전달될 때, Reference Variabale(참조변수)의 복사 값이 생성되어 메서드로 전달됩니다. 

Reference Variabale(참조변수)의 복사 값은 원래의 참조변수와 같은 객체를 가리킵니다. 

따라서 메서드에서 객체의 상태를 변화시키면 원래의 객체에 영향을 끼칩니다.

하지만 메서드에 전달된 참조변수에 새로운 객체를 할당하면 원래의 객체에는 더 이상 영향이 가지 않습니다.

 

[Code] 

public class NonPrimitive {
    public static void main(String[] args) {
        CustomNumber a = new CustomNumber(5);
        CustomNumber b = new CustomNumber(10);
        print("[before]", a.getNumber(), b.getNumber());
        swap(a, b);
        print("[after]", a.getNumber(), b.getNumber());
    }

    private static void swap(CustomNumber a1, CustomNumber b1) {
        a1.number++;

        b1 = new CustomNumber(10);
        b1.number++;
        print("[swap]", a1.getNumber(), b1.getNumber());
    }
    private static void print(String status, int a, int b) {
        System.out.println(status + " a: " + a + ", b : " + b);
    }

    private static class CustomNumber {
        private int number;
        public CustomNumber(int number) {
            this.number = number;
        }

        public int getNumber() {
            return number;
        }
    }
}

[실행 결과]

[before] a: 5, b : 10
[swap] a: 6, b : 11
[after] a: 6, b : 10

 

[동작원리]

1. main()가 실행될 때, a, b 에는 Object를 참조하는 참조변수가 Stack Memory에 저장됩니다. 

2. swap() 메서드가 실행되면 참조변수를 복사하여 메서드에 전달합니다. 

3. swap() 메서드 내에서 로직이 수행된 후의 상황입니다.

ref a1의 경우 메서드 내에서 상태를 변경하면 원래의 객체에도 변화가 생깁니다. 
하지만 b1의 경우 새로운 객체 b1을 할당해 주었습니다. 
따라서 더 이상 원래 객체를 바라보지 않기 때문에 메서드에서 상태를 변경해도 원래의 객체인 b는 변화하지 않습니다.

Conclusion

Java에서는 method에 인자를 넘길 때 어떻게 넘기는지 알아보았습니다. 

  • Primitive Value
    • 값 자체를 복사하여 메서드의 변수로 넘겨준다.
  • Non-Primitive Value
    • 참조 변수 값을 복사하여 메서드의 변수로 넘겨준다. 

Non-Primitive Value의 경우 조금 헷갈릴 순 있습니다.

하지만 예제에서 알 수 있듯 복사된 참조값에 새로운 객체를 할당하면 더 이상 원래의 객체에 영향을 받지 않는 것을 확인할 수 있습니다.

 

이번 장에서 사용한 예제는 다음 링크를 확인해주세요 

Github 바로가기

 

GitHub - BeomSeogKim/java-defrag: java 기능 관련 모음

java 기능 관련 모음. Contribute to BeomSeogKim/java-defrag development by creating an account on GitHub.

github.com


출처 :

[1]Wikipedia

[2]https://www.baeldung.com/java-pass-by-value-or-pass-by-reference