ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Call by Value / Call by Reference (feat. primitive )
    Java 2023. 9. 13. 17:04

    C/C++에는 주소값을 활용한 포인터  방식으로 대상을 찾아간다.

    자바 또한  참조값(메모리 주소값)을 통해 찾아가는 방식이다.

     

    두 방식은 거의 같아보이는데, 주요 차이점은

    포인터는 메모리 주소를 임의로 변경할수 있고, 주소값을 직접 제어해서 변수간 이동하는 방식이다. 
    반면 자바는  jvm이 메모리를 관리해주기 때문에 메모리 주소 임의 변경이 불가능하고 메모리 주소를 맘대로 제어할 수 없다.

    JVM의 영역별 역할을 아는것이 call by ~를 이해하는 데 도움이 될것이다.

    Call by Value

    Primitive type 에는 가장 기본적인 int , long , char , boolean , byte, float, double 이 있다. 

     

    참조 주소값 없이, 실제 데이터만을 저장한다.

     

    해당 변수들은 모두 jvm내의 stack 영역에 저장된다. stack 영역은 메서드 내의 인자나 변수같이 해당 메서드 영역내에서만 사용할 수 있다.

     

      public static void main(String[] args) {
           
            int k = 0;
            change_pri(k);
    
            System.out.println(k); // 0
    
        }
    
        public static void change_pri(int a) {
            a +=100; 
        }

     

    메서드마다 다른 stack영역을 갖기 때문에 생기기 때문에 ,

     

    change_pri 메서드에서 인자로 받는 a라는 변수는  main 메서드에서 넘겨준 '값만 복사한' 원본과는 무관한 새로운 변수다. 

     

    따라서 change_pri의 a는 100이지만 main에서 print하는 a는 당연히 0이 나오게 된다.

     

    Call by value는 보통 primitive 타입을 이용하는 경우를 일컫는다.

     


    Call by Reference ( 사실 자바는 모두 Call by Value ?)

    사실 자바에서 모든 호출 방식은 Call By Value다. 
    결과적으로 보면 파라미터 전달시 객체의 주소'값'을 넘겨준다. Call By Reference는  객체 그 자체가 넘어가는 것이다.
    그럼에도 Reference 라 써놓은 이유는 primitive타입과 구분하고자 적어놓았다.

    더 자세한 설명은 이곳을 참고하자.

     

    Java에서 Reference타입들은 Primitive 타입을 제외한 타입들*(문자열, 배열, 열거, 클래스 등..)*을 말한다.

     

    해당 Reference 타입들은 객체로서 생성되기 때문에 모두 Heap영역에 올라간다.

     

    그리고 Stack영역에는 실 객체를 가리키는 레퍼런스 타입 변수가 생성된다. 해당 변수는 heap영역에 있는 객체의 주소값을 갖고 있다.

     

    그렇다면 이 Reference 타입변수들이 이용될때는 어떻게 이용되는지 알아보자.


    class TestApplication {
    
        public static void main(String[] args) {
            User user = new User(10);
            change_age(user);
            System.out.println(user.age); // 실제 결과 = 50 / 기대 결과 = 50
        }
    
        public static void change_age(User user) {
            user.age = 50;
        }
    
    }
    class User{
        int age ;
    
        public User(int age) {
            this.age = age;
        }
    }

     

    User 생성자에는 10으로 초기화했다.

    change_age 메서드에서 user를 넘겨주고 user의 나이를 50으로 바꿔주었다. 

     

    이후 main 영역에서 나이는 50으로 나온다.

     

    이 동작이 가능한 이유는

    User 실제 객체는 Heap영역에 저장되고 ,

     

    main()에 있는 user와 change_age()에 있는 user는 같은 Heap영역의 User를 가리키고 있기 때문이다.

    (main에서 change_age()실행시 넘겨주는 파라미터에는 참조(주소)값이 들어있다)

    이전 Call by Value에서 보았던 값 복사 과정은 당연히 없다.


    class TestApplication {
    
        public static void main(String[] args) {
            int[] list = new int[]{0};
            change_String(list);
            System.out.println(list[0]);   // 실제결과: 1000 / 기대결과: 1000
        }
    
        public static void change_String(int[] s) {
            s[0] = 1000;
        }
    
    }

     

    int/long 등의 배열는 primitive타입 같지만

    Reference 타입이기에 우리가 기대하는 결과를 얻을 수 있다.

     


    - 조심해야할 부분

    class TestApplication {
    
        public static void main(String[] args) {
            String s = "123";
            change_String(s);
            System.out.println(s); //실제 결과: 123 / 기대 결과: haha
        }
    
        public static void change_String(String s) {
            s = "haha";
        }
    
    }

    레퍼런스 타입인 String에도 해보았으나

    기대한 결과는 나오지 않았다.

     

    그 이유는 String, Integer, Long 등 wrapper클래스는 '불변객체'다.

    값이 변하는것처럼 보여도 실제로는 new로 새롭게 생성된 객체라는 것이다.

     

    따라서 우리가 기대한 결과와는 다르게 wrapper클래스에서는 값을 변경할 수 없었다. 

     

     

    Reference

     

    - https://www.youtube.com/watch?v=lRUiaigGIDA 

    - https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%9E%90%EB%B0%94%EB%8A%94-Call-by-reference-%EA%B0%9C%EB%85%90%EC%9D%B4-%EC%97%86%EB%8B%A4-%E2%9D%93

    - https://velog.io/@conatuseus/Java-Immutable-Object%EB%B6%88%EB%B3%80%EA%B0%9D%EC%B2%B4

Designed by Tistory.