거북이처럼 천천히

Java - call by value & call by reference 본문

Back-end/Java 개념

Java - call by value & call by reference

유로 청년 2022. 6. 21. 01:09

문제점 발생

목표 : n개 정수를 입력받아 배열에 저장한 뒤, 버블정렬(Bubble sort) 알고리즘을 이용하여 오름차순 정렬후, 출력

import java.util.Scanner;

public class programming {
	public static void swap(int a, int b) {
		int tmp = a;
		a = b;
		b = tmp;
	}
	
	public static int[] sortAscending(int[] data, int size) {
		for(int j=size-1; j>0; j--)
			for(int k=0; k<j; k++) 
				if(data[k]>data[k+1]) {
					swap(data[k], data[k+1]);
				}	
		return data;
	}
	
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		
		// 배열 사이즈 입력 & 배열 초기화
		System.out.print("배열의 사이즈 입력:");
		int size = scan.nextInt();
		int[] data = new int[size];
		
		// 배열 원소 입력
		for(int i=0; i<size; i++)
			data[i] = scan.nextInt();
		
		// 배열 정렬
		data = sortAscending(data, size);
		
		//결과 출력
		System.out.println("Result: ");
		for(int i=0; i<size; i++)
			System.out.printf("%d ", data[i]);
	}
}

하지만, 이전값(data[k])과 다음값(data[k+1])의 자리 바꿈(swap)하는 과정에서 자리 바뀌지 않는 문제점이 발생하였다. 

왜 swap 메소드는 동작하지 않았는가? 해결책은 무엇인가? 

이를 이해하기 위해서는 call by value & call by reference를 정확하게 이해할 필요가 있다.


Call by value

call by value에 앞서 호출문의 parameter(=argumnet)와 호출된 메소드의 parameter(=parameter)의 명칭을 살펴볼 필요가 있다. 아래와 같이 호출문의 parameter를 actual parameter, 호출된 메소드의 parameter를 formal parameter라 한다.

호출문의 parameter(=argument) : actual parameter
호출된 메소드의 parameter : formal parameter

swap 메소드에 전달하는 배열의 값(data[j], data[j+1])이 actual parameter이며, swap 메소드 내에서 받는 a와 b가 formal parameter라 할 수 있다. 

 

그래서 swap 메소드를 호출하면 호출될 때, 메모리 공간내에서 메소드를 위한 별도의 임시공간이 생성된다. (종료시 반환)

Call by value 호출 방식은 함수 호출시 전달되는 변수 값을 복사해서 메소드 argument(=formal parameter)로 전달한다.

이때의 복사된 argument(=formal parameter)는 함수 안에서만 지역적으로 사용되기 때문에 local value 속성을 갖는다. 

 

즉, Call by value 호출 방식에서 전달하고자 하는 값을 복사해서 전달하기 때문에 복사된 formal parameter(=argument)와 실제 배열를 구성하는 원본, actual parameter는 메모리내에서 완전히 별개의 자리를 차지하는 변수이며, 이 뜻은 두 변수는 완전히 다른 변수임을 의미한다.

 

  • Call by value 호출 방식으로 호출하면 원본 값을 복사하여 formal parameter에 대입
  • 하지만, 대입한 이후에는 두 변수는 메모리내에서 완전히 별개의 자리를 차지한다.
  • 즉, 두 변수는 완전히 다른 변수이라는 뜻이다. 
  • Primitive data type의 매개변수(parameter)는 호출된 매서드내에 값을 변경하더라도 호출된 쪽(원본)에는 영향을 주지 못한다. 이것은 "Call by value"이기 때문이다.
  • 즉, Primitive data type은 오직 call by value만 가능하기 때문에 primitive data type을 갖는 매개변수는 메소드내에서 아무리 값을 변경하여도 call by value 호출 방식을 이용하기 때문에 호출한 쪽(원본)에는 영향을 주지 못한다.

 

분명히 formal parameter인 a와 b내에서는 swap되었다. 하지만, 복사본인 formal parameter에서만 swap될 뿐, 원본인 actual parameter(data[j], data[j+1])에서는 swap되지 않았다. (왜냐하면 formal와 actual은 완전히 다른 변수이기 때문) 따라서 배열, data도 swap되지 않아 오름차순으로 정렬되지 못하였다.

 


Call by reference

call by value는 복사된 값을 함수의 인자(formal parameter)에게 전달한다면, call by reference는 함수 호출 시 인자로 전달되는 변수의 레퍼런스를 전달한다.

 

 

예를 들어 int[ ] data를 parameter로 전달하면, 정수형 배열은 primitive 타입이 아닌기 때문에 data는 참조 변수이고, 메소드의 argument에서 data2로 받으면 data2 또한 참조 변수인 것이다. 따라서 data2는 정수형 배열의 주소(참조) 값을 복사하여 갖게 되고, data와 data2는 참조 변수로서 동일한 배열을 가르키게 된다. 이런 상태에서 매소드 내에서 배열를 수정하면 원본 배열을 가지고 수정하는 것이기 때문에 메소드내에서의 배열 수정은 메소드 밖에서도 수정된 결과가 유지되는 것이다.

 

따라서 actual parameter와 formal parameter가 서로 다른 변수가 되지 않고, foraml parameter가 actual parameter를 가리켜서 단지 하나의 데이터에 두 개의 이름이 있는 것과 같다.

 

사실 Call by reference 호출방식은 C나 Java에서 지원하지 않은 호출방식이다. 그래서 주소값을 넘겨주는 방식도 없다.

따라서 Java에서는 오로지 Call by value 밖에 존재하지 않는다.

 

하지만, sortAscending 메소드를 호출하고, sortAscending 메소드내에서 data[] 배열을 정렬 및 변경하면 호출한 쪽(main 메소드)에서도 바뀌는 것을 확인 할 수 있다. 이것은 무엇인가?

 

사실은 Primitive data type가 아닌 데이터 타입를 인자로 넣어준다면 값이 변하게 할 수 있다.

(배열과 String 등은 primitive data type이 아니다.)

 

import java.util.Scanner;

public class programming {
	public static void swap(int k, int data[]) {
		int tmp = data[k];
		data[k] = data[k+1];
		data[k+1] = tmp;
	}
	
	public static int[] sortAscending(int[] data, int size) {
		for(int j=size-1; j>0; j--)
			for(int k=0; k<j; k++) 
				if(data[k]>data[k+1]) {
					swap(k, data);
				}
					
		return data;
	}
	
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		
		// 배열 사이즈 입력 & 배열 초기화
		System.out.print("배열의 사이즈 입력:");
		int size = scan.nextInt();
		int[] data = new int[size];
		
		// 배열 원소 입력
		for(int i=0; i<size; i++)
			data[i] = scan.nextInt();
		
		// 배열 정렬
		data = sortAscending(data, size);
		
		//결과 출과
		System.out.println("Result: ");
		for(int i=0; i<size; i++)
			System.out.printf("%d ", data[i]);
	}
}

정리하면 다음과 같다.

  • Primitive data type 매개변수는 호출된 매소드내에서 아무리 값을 변경하여도 호출한 쪽(원본)에는 영향을 주지 못한다. 그 이유는 "Call by value" 호출 방식을 이용하기 때문이다.
  • 하지만, Primitive data type이 아닌 매개변수(배열 등)은 "Call by reference" 호출 방식을 이용하기 때문에 호출된 메소드내에서 값을 변경하면 호출한 쪽(원본)에도 영향을 끼친다.

 


예시

package Chapter2;

class Update {
	void add(int count) {
		count++;
	}
}

class Counter {
	int number = 0;
}

public class sample {
	public static void main(String[] args) {
		Counter sampleOne = new Counter();
		Update updateMethod = new Update();
		
		System.out.println("Before: "+sampleOne.number);
		
		updateMethod.add(sampleOne.number);
		
		System.out.println("After: "+sampleOne.number);
	}
}

Counter 클래스를 통해 sampleOne 객체를 생성한 뒤, Update 클래스의 add 매소드를 이용하여 sampleOne 객체의 number 객체 변수(=인스턴스 변수)를 하나 증가시키고자 하였지만, 결과는 변하지 않았다.

 

그 이유는 add 메소드가 값을 primitive data type인 int형으로 받아 Call by value 호출 방식을 이용하였기 때문이다. 

 

package Chapter2;

class Update {
	void add(Counter count) {
		count.number++;
	}
}

class Counter {
	int number = 0;
}

public class sample {
	public static void main(String[] args) {
		Counter sampleOne = new Counter();
		Update updateMethod = new Update();
		
		System.out.println("Before: "+sampleOne.number);
		
		updateMethod.add(sampleOne);
		
		System.out.println("After: "+sampleOne.number);
	}
}
Before: 0
After: 1

 

이전 예제와의 차이점은 add 메소드의 입력항목이다. add 메소드는 입력 값으로 객체를 입력 받았기 때문에 call by value가 아닌 call by reference 호출 방식을 이용하였으며, 이를 통해 메소드내에서도 변수 값을 변경하는 동시에 변경된 값을 유지시킬 수 있었다.

 

이렇게 메소드의 입력으로 객체를 전달받는 경우에는 메소드가 입력받은 객체를 그대로 사용하기 때문에 메소드가 객체의 속성값을 변경하면 메소드 수행 후에도 객체의 변경된 속성값이 유지된다.

'Back-end > Java 개념' 카테고리의 다른 글

Java - 클래스 정리 (1)  (0) 2022.06.26
Java - 변수 선언 위치에 따른 변수 구분  (0) 2022.06.26
Java - next(), nextLine() 차이  (0) 2022.06.14
Java - for each 문  (0) 2022.06.12
Java - List (2)  (0) 2022.06.03