Language/Java

[JAVA] Call by Value VS Call by Reference

anxi 2024. 7. 19. 00:04

오랜만에 블로그 작성하네요,,,

회사 적응 중이라 개인 공부를 하기 쉽지 않네요.. 는 변명이고 퇴근하고 집에 오면 너무 졸려요...ㅜ

 

뜬금없이 작성하는 이유는? 

어제 코드 리뷰를 받다가 제가 이러한 코드를 작성했습니다.

// 예시
public static Computer of(Cpu cpu) {
    
    cpu = cpu != null ? cpu : gpu; // gpu는 cpu와 다른 예시 값입니다.
    ...
}

 

코드 작성할 때

"새로운 객체 생성하는 것보다 매개변수 객체를 재사용 하는 것이 낫겠군..!" 과 같이 생각하고 짯는데

 

메소드 개발 시 매개변수에 값을 할당하는 로직은 위험합니다
참조 객체에 메소드 실행 후 set처리 되어 값 조작이 발생 가능합니다

 

위와 같은 코드리뷰를 받았습니다. 

곰곰이 생각해보니 예전 C++ 전공 수업 때 배운 Call by Value와 Call by Reference가 생각났습니다.

 

결국 새로운 객체를 생성해서 매개변수 값을 조작하지 않고 작업 마무리를 했지만

자바의 경우 Call by Value로 동작하는지, Call by Reference로 동작하는지 모르고 있었습니다.

(면접 준비할 때 대충 Call by Value로 동작하는 것만 알 뿐 왜 그런지는 알지 못함)

 

이번 기회에 찾아보면서 자바가 함수에서 값을 어떤 방식으로 호출하는지 자세히 알고 싶어서 정리하게 되었습니다.


Call by Value

  • 값에 의한 호출이라고 하며 함수 호출 시 인수의 값을 복사하여 전달하는 방식을 의미한다.
  • Pass by Value 라고도 부른다.
  • 실제 인수가 아닌 인수의 복사본이 함수에 전달되어 함수에서 인수의 값이 변경되어도 원래 인수에는 영향을 미치지 않는다.
  • 자바에서 원시 타입(Primitive) 전달 시 Call by Value로 호출한다.
public class CallByValue {

  public static void main(String[] args) {
    
    int og = 10;
    modify(og);
    System.out.println(og);
  }
  
  private static void modify(int call) {
    
    call = 100;
    System.out.println(call);
  }
}

// 결과
100
10

 

과정

  1. og 변수가 초기화된다. 이때 스택에 og 변수와 값 10이 저장된다.
  2. modify 메서드가 실행된다.
    1. og의 변수를 호출하여 call이라는 변수와 값 10이 스택에 저장된다. (복사본)
    2. call 변수의 값이 100으로 변경된다.
    3. call의 값을 노출함으로 100이 출력된다.
  3. og의 값인 10이 출력된다.

결론

스택에 변수가 저장되며 원본과 값에 의해 호출된 복사본은 서로 독립되어 실행된다.


Call by Reference

  • 참조에 의한 호출이라고 하며 함수 호출 시 인수의 참조(주소) 자체가 전달되는 방식을 의미한다.
  • 함수 내에서 인수의 값이 변경되면 그 변경이 실제 인수에 영향을 미친다.
    • 원본과 인수는 같은 객체의 실제 데이터의 주소를 가리키고 있어 함수 내부의 수정이 원래 데이터에 반영된다.
class Computer {
    int cpu;

    Computer(int cpu) {
        this.cpu = cpu;
    }
}

public class Main {
   
    public static void main(String[] args) {
        Computer obj = new Computer(10);
        System.out.println("Before: " + obj.cpu);
        modify(obj);
        System.out.println("After: " + obj.cpu);
    }
    
    public static void modify(Computer call) {
        call.cpu = 10000;
    }
}

// 결과
Before 10
After 10000

 

과정

  1.  cpu가 10인 Computer 객체가 힙 메모리에 생성된다. 
    • 이때 obj는 참조 변수로 스택에 생성되며, 힙 메모리에 생성된 Computer 객체를 가리킨다.
  2. obj의 cpu인 10을 출력한다.
  3. modify 메서드가 실행된다.
    1. call 이라는 참조 변수를 스택에 생성한다. 참조 변수는 Call by Reference로 호출한 힙 메모리에 생성된 Computer 객체를 참조한다. call은 obj의 객체 참조를 전달받는다. (밑줄 친 방식은 Call by Value 방식)
    2. call의 참조 변수는 참조하는 객체의 cpu 값을 10000으로 변경한다.
  4. obj가 참조하는 객체의 값이 변경되었으므로 10000을 출력한다.

결론

객체 생성 시 힙 메모리에 객체가 저장되며, 참조 변수는 힙 메모리에 생성된 객체를 참조한다. (가리킨다)


그럼 자바는 원시 타입일 때는 Call by Value이고 레퍼런스 타입일 때는 Call by Reference 인가?

 

정답은 아니다!

 

자바는 원시 타입이든 레퍼런스 타입이든 Call by Value 로 값을 호출한다.

 

class Computer {
    int cpu;

    Computer(int cpu) {
        this.cpu = cpu;
    }
}

public class Main {
   
    public static void main(String[] args) {
        Computer obj = new Computer(10);
        System.out.println("Before: " + obj.cpu);
        modify(obj);
        System.out.println("After: " + obj.cpu);
    }
    
    public static void modify(Computer call) {
        call = new Computer(101010); // 새로운 객체 생성
    }
}

// 결과
Before 10
After 10

 

위 상황에서 바뀐 부분은 modify 함수에서 인수를 새로운 Computer 객체로 생성한다.

 

Call by Reference의 과정으로 보면

  1.  cpu가 10인 Computer 객체가 힙 메모리에 생성된다. 
    • 이때 obj는 참조 변수로 스택에 생성되며, 힙 메모리에 생성된 Computer 객체를 가리킨다.
  2. modify 함수가 호출될 때 obj의 참조가 call로 복사되지 않고, obj 자체가 전달된다. (복사 X, 자체 전달)
  3. call이 새로운 객체를 가리키도록 변경하면, obj도 동일한 새로운 객체를 가리킨다.
  4. obj의 cpu 값을 출력할 때 새로운 객체의 값인 101010이 출력된다.

하지만 결과는 101010이 아닌 10이 출력된다.

 

만약 Call by Value 방식으로 참조를 복사해서 전달하면 어떻게 될까?

  1.  cpu가 10인 Computer 객체가 힙 메모리에 생성된다. 
    • 이때 obj는 참조 변수로 스택에 생성되며, 힙 메모리에 생성된 Computer 객체를 가리킨다.
  2. modify 함수가 호출될 때 obj의 참조가 call로 복사되어 전달된다.
  3. call이 새로운 객체를 참조하여도, obj는 기존의 원래 객체를 가리킨다.
  4. obj의 cpu 값을 출력할 때 기존 객체의 값인 10이 출력된다.

최종 결론

  • Call by Value
    • 값의 복사본을 인수에 전달하는 방식
    • "복사"에 집중
  • Call by Reference
    • 객체 자체를 인수에 전달하는 방식
    • "자체"에 집중
  • 자바는 Call by Value를 이용한다. 
    • 원시 타입일 경우, 값 자체가 복사되어 전달된다. 
    • 레퍼런스 타입일 경우, 객체를 함수에서 호출할 때 객체 자체(Call by Reference)가 아닌 객체의 참조(주소)를 복사(Call by Value)해서 전달된다. 즉, Value가 참조라고 생각하면 된다.