본문 바로가기

IT

Pass By Value or Pass By Reference?

반응형
자바에서 Pass by Refrence 와 Pass by Value

참조 : http://www.impunity.co.kr/~rfm98/ver3/main.html

Jstorm에서 가져온 내용입니다.

며칠전 Jstorm세미나를 하다가 한 친구가 자바는
Pass by Value인지 아니면 Pass by Reference인지를 물어봤습니다.
그래서 저는 두서없이 primitive type은 Pass by Value이고
객체는 Pass by Reference라고 대충 말해줬습니다.
하지만 사실 엄밀한 의미에서 말하면 모든 자바에서의 값넘김은
Pass by Value이죠... 어쨌든 그 미묘한 차이를 이해하는 것은
의외로 상당히 중요할 듯 싶어서 외국 문서 몇개를 뒤져서
쉽게 설명이 가능하도록 글을 써봅니다.

자바에서의 Pass by Value의 의미란?

아래 예제를 먼저 볼까요?


class Test {
public static void main(String args[]) {
int val;
StringBuffer sb1, sb2;
val = 10;
sb1 = new StringBuffer("apples");
sb2 = new StringBuffer("pears");
System.out.println("val is " + val);
System.out.println("sb1 is " + sb1);
System.out.println("sb2 is " + sb2);
System.out.println("");
System.out.println("calling modify");
//All parameters passed by value
modify(val, sb1, sb2);
System.out.println("returned from modify");
System.out.println("");
System.out.println("val is " + val);
System.out.println("sb1 is " + sb1);
System.out.println("sb2 is " + sb2);
}
public static void modify(int a, StringBuffer r1, StringBuffer r2) {
System.out.println("in modify...");
a = 0;
r1 = null;
//1
r2.append(" taste good");
System.out.println("a is " + a);
System.out.println("r1 is " + r1);
System.out.println("r2 is " + r2); } }
아래는 결과입니다.


val is 10
sb1 is apples
sb2 is pears

calling modify
in modify...
a is 0
r1 is null
r2 is pears taste good
returned from modify

val is 10
sb1 is apples
sb2 is pears taste good
예제코드에서는 세개의 변수를 선언하고 있습니다. 하나는 int이고
두개는 객체이지요. 일단 각 변수들을 초기화하고 출력합니다.
그리고선 그 변수들은 modify함수에 인자값으로 넘어가게 됩니다.

그러면 modify함수는 인자로 넘어온 세 값들을 변경시키고는 출력합니다.
main으로 다시 돌아오면 세 변수들은 다시 한번 출력합니다.

int형인 val값은 당연히 pass by value이므로 바뀌지 않았습니다.
그런데 이상한 것은 sb1객체입니다. 위에서도 제가 한 친구에게 말했듯
자바에서 객체는 pass by Reference라고 했는데 왜 안바뀌었을까요?
사실 자바에서 객체는 참조(Reference)값의 복사본을 Pass by Value로
넘긴답니다. 따라서 modify함수에서 sb1의 참조값을 변경하더라도
main으로 돌아오면 그 참조값은 예전 그대로 살아있는 것입니다.

그러면 두번째 객체 참조값인 sb2를 살펴봅시다. 비록 sb2의 참조값의
복사본이 modify에 넘겨졌지만 어쨌든 두 객체는 같은 곳을 참조하고
있기때문에 객체를 변화시키는 일련의 행위들은 동일하게 작용하게
되는 것이죠.

그러면 이제 자바를 이용해서 swap 함수를 만들어봅시다.
아래 예제가 잘 실행이 될지 생각해보세요.


class Swap {
public static void main(String args[]) {
Integer a, b;
a = new Integer(10);
b = new Integer(50);
System.out.println("before swap...");
System.out.println("a is " + a);
System.out.println("b is " + b);
swap(a, b);
System.out.println("after swap...");
System.out.println("a is " + a);
System.out.println("b is " + b);
}
public static void swap(Integer a, Integer b) {
Integer temp = a;
a = b;
b = temp;
}
}
위에서도 지적했듯, 자바는 모든 값넘김을 value로 하기 때문에
이 코드는 우리가 생각했던 대로 동작하지 않을 겁니다.

결과


before swap...
a is 10
b is 50
after swap...
a is 10
b is 50

JDC Tech Tip의 원문은 다음의 Web 사이트에서 보실 수 있습니다.
http://java.sun.com/jdc/TechTips/2000/tt1205.html

JDC Tech Tip 2000년 12월 5일

이 팁은 JAVA 2 SDK , Standard Egition v 1.3에서 개발되었습니다.
이번 JDC Tech Tip은 Glen McCluskey에 의해 작성되었습니다.

메소드에서 여러개의 값 리턴하기 ( RETURNING MULTIPLE VALUES FROM METHOD)

Java 프로그램으로 변환하고 싶은 C++ 코드가 있다고 하자. 변환하는도중, 다음과 같은 코드와 만날수 있다.

#include

void getStats(int data[], int& count, int& sum, int& mean) {
count = 0;
sum = 0;
mean = 0;

for (int i = 0; data[i] ! = -1; i++) {
count++;
sum += data[i];
}

if (count > 0) {
mean = sum / count;
}
}

int main() {
int data[] = {10, 17, 39, -1};
int count;
int sum;
int mean;

getStats(data, count, sum, mean);

printf("count = %d sum = %d mean = %d", count, sum, mean);

return 0;
}

getStats 함수는, -1 로 끝나는 정수의 리스트를 입력으로서 받아, 리스트 내의 정수의 개수, 합계 및 평균치를 계산합니다.
이 3 개의 값은 참조 파라미터로 리턴됩니다. 예를 들어, "int&" 는 "정수에의 참조" 라고 읽을 수 있습니다.

Java 프로그래밍 언어를 이용해서는 이 프로그램에 직접 대응할 수 없습니다.
그러나, Java 메소드로부터 복수의 값을 돌려주는 필요가 있다면, 몇개의 방법을 사용 할 수 있습니다.
이러한 접근방법을 실제로 보기 전에, 값과 참조 파라미터에 관한 기본적인 문제를 검토해 둘 필요가 있습니다.

Java 프로그래밍에서는, 모든 메소드 파라미터는 pass by value로 건네받습니다.
즉, 실행중인 메소드에 의해 정해진 인수의 복사본이 만들어집니다.
다음과 같은 메소드가 있는 경우,

void f(int i) {}

다음과 같이 호출하면,

int j = 37;
f(j);

j 의 주소는 아니고, 37 이라고 하는 실제 값을 받습니다. 따라, 호출측의 j 의 값에 영향을 주는 방법은 없습니다.

또, 값에 의한 호출은, 객체 참조가 되는 파라미터에도 적용됩니다.

다음과 같은 메소드가 있는 경우,

void f(ArrayList list) {}

다음과 같이 호출하면,

ArrayList mylist = new ArrayList();
f(mylist);

여기에서도 mylist 에의 참조의 복사본을 건네받습니다.
메소드 내부에서 참조의 값을 변경하는 경우는, 파라미터 변수만, 즉 list 의 값이 변경됩니다.
이것은, list 가 mylist 의 pass-by-value 복사본으로 있기 때문입니다. 예를 들어, 다음과 같이 썼다고 하면,

void f(ArrayList list) {
list = null;
}

단순하게 list 의 값이 null로 변경될뿐입니다.
호출측 mylist 의 값에는 아무런 영향도 없습니다. mylist 의 값은, f 가 호출 되기 전과 같습니다.

그러나, 메소드내에서는 넘겨받은 객체의 내용 또는 상태를 변경할 수가 있습니다.
예를 들어, 다음과 같이 쓸 수가 있습니다.

void f(ArrayList list) {
list.add(new Integer(37));
}

이 코드는, 실제의 ArrayList 객체의 내용을 변경합니다.
그 결과, 이 객체의 참조를 가진 프로그램의 모든 부분에서, 객체가 변경됩니다.
덧붙여 여기서 변경하고 있는 것은 객체의 내용이며, 넘겨받은 참조의 값이 아닙니다.

이상의 기본지식을 가지고, 위의 C++ 코드를 Java 어플리케이션으로 사용하기 위해 변환 하려고 할 때,
어떻게 하면 1 개의 메소드로부터 여러개의 값을 리턴 할 수 있을까를 생각해 봅시다.
우선 첫번째 접근방법은, 다음과 같이 3 개의 정수 배열을 만드는 방법입니다.

public class RetDemo1 {

static int[] getStats(int data[]) {
int count = 0;
int sum = 0;
int mean = 0;

for (int i = 0; i < data.length; i++) {
count++;
sum += data[i];
}

if (count > 0) {
mean = sum / count;
}

int retvals[] = new int[3];
retvals[0] = count;
retvals[1] = sum;
retvals[2] = mean;

return retvals;
}

public static void main(String args[]) {
int data[] = new int[]{10, 17, 39};

int retvals[] = getStats(data);

System.out.println("count = " + retvals[0] +
" sum = " + retvals[1] + " mean = " + retvals[2]);
}
}

RetDemo1 프로그램을 실행하면(자), 다음과 같은 출력을 얻을 수 있습니다.

count = 3 sum = 66 mean = 22

3 개의 배열 위치는 갯수, 합계 및 평균을 보관 유지하기 위해서 사용됩니다.
이 방법은, 모든 반환값이 같은 데이터형태(이 경우는 int)인 경우에 적합합니다.
그러나, 다음의 RetDemo2 프로그램이 보여주는 방법만큼 자연스러운 것은 아닙니다.

C++ 에서도 유사한 테크닉을 사용하는 것은 가능합니다만, 하나의 큰 차이가 있습니다.
C++ 에는 가비지 콜렉션이 없습니다. 이 때문에, retvals 배열의 할당해제를 명시적으로 관리할 필요가 있습니다.
한편, Java 의 가비지 콜렉션을 사용하면, 이 귀찮은 문제는 자동적으로 처리됩니다.

다음 방법은 내부 클래스를 사용해, 거기에 대응하는 형태의 객체를 돌려줍니다.

public class RetDemo2 {

static class Retval {
int count;
int sum;
double mean;
}

static Retval getStats(int data[]) {
int count = 0;
int sum = 0;
double mean = 0.0;

for (int i = 0; i < data.length; i++) {
count++;
sum += data[i];
}

if (count > 0) {
mean = (double) sum / count;
}

Retval ret = new Retval();
ret.count = count;
ret.sum = sum;
ret.mean = mean;

return ret;
}

public static void main(String args[]) {
int data[] = new int[]{10, 17, 39};

RetDemo2.Retval ret = getStats(data);

System.out.println("count = " + ret.count +
" sum = " + ret.sum + " mean = " + ret.mean);
}
}

이 예제에서, RetDemo2.Retval 는, count , sum 및 mean 을 보관 유지하기 위해서 정의 된 필드를 포함한 내부 클래스입니다.
이것은, 일반적인 상황에 가장 적합한 방법이라 생각됩니다.
이 접근방법은 RetDemo1 보다 유연하고, 복수의 필드가 같은 데이터형태일 필요도 없습니다.
이 예제에서는, 「mean」의 데이터형태가 int 로부터 double로 변경되어 있습니다.
내부 클래스를 사용하면, 자연스러운 방법으로 관련되는 데이터 항목을 정리해 클래스 객체에 그룹화 할 수가 있습니다.

내부 클래스가 리턴값 객체로 사용되는 이 예제에 요구사항은 없습니다.
이 때문에 top-level 클래스를 정의 할 수 있습니다.
그러나 일반적으로 클래스가 다양한 어플리케이션에 걸쳐서 일반적으로 사용되는 경우가 아니라면,
내부 클래스를 사용해, global 이름 공간의 혼란을 피하는 것이 좋습니다.

덧붙여 Retval 클래스의 객체는 단지 데이터의 운반 수단으로 보고 사용됩니다.
생성자나 접근 메소드는 없습니다. 이것은 스타일의 문제입니다.
경우에 따라서는, Retval 의 필드를 private 로 하고 클래스에 대한 생성자나「getCount」와 같은 접근 메소드를
정의 해야 할 수도 있습니다.

3 번째의 방법에서는, ArrayList 객체를 사용해 복수의 값을 돌려줍니다.

import java.util. *;

public class RetDemo3 {

static List getStats(int data[]) {
int count = 0;
int sum = 0;
int mean = 0;

for (int i = 0; i < data.length; i++) {
count++;
sum += data[i];
}

if (count > 0) {
mean = sum / count;
}

List list = new ArrayList();
list.add(new Integer(count));
list.add(new Integer(sum));
list.add(new Integer(mean));

return list;
}

public static void main(String args[]) {
int data[] = new int[]{10, 17, 39};

List list = getStats(data);

System.out.println("count = " + list.get(0) +
" sum = " + list.get(1) + " mean = " + list.get(2));
}
}

마지막 방법은 3 번째의 방법과 유사하지만, getStats 메소드에 파라미터로서 넘겨받은 참조를 이용해서
ArrayList 객체를 갱신합니다.

import java.util. *;

public class RetDemo4 {

static void getStats(int data[], List list) {
int count = 0;
int sum = 0;
int mean = 0;

for (int i = 0; i < data.length; i++) {
count++;
sum += data[i];
}

if (count > 0) {
mean = sum / count;
}

list.add(new Integer(count));
list.add(new Integer(sum));
list.add(new Integer(mean));
}

public static void main(String args[]) {
int data[] = new int[]{10, 17, 39};

List list = new ArrayList();
getStats(data, list);

System.out.println("count = " + list.get(0) +
" sum = " + list.get(1) + " mean = " + list.get(2));
}
}

RetDemo4 는, 기존의 리스트에 값을 추가하는 경우 가장 위력을 발휘합니다.

이 방법은, 이전 방법들과 같이 사용 할 수 있습니다.
예를 들어, RetDemo2 와 RetDemo4 를 같이 사용하면, 다음과 같이 됩니다.

import java.util. *;

public class RetDemo5 {

static class Retval {
int count;
int sum;
int mean;
}

static void getStats(int data[], List list) {
int count = 0;
int sum = 0;
int mean = 0;

for (int i = 0; i < data.length; i++) {
count++;
sum += data[i];
}

if (count > 0) {
mean = sum / count;
}

Retval ret = new Retval();
ret.count = count;
ret.sum = sum;
ret.mean = mean;

list.add(ret);
}

public static void main(String args[]) {
int data1[] = new int[]{10, 17, 39};
int data2[] = new int[]{19, 26, 57};

List list = new ArrayList();
getStats(data1, list);
getStats(data2, list);

for (int i = 0; i < list.size(); i++) {
RetDemo5.Retval ret = (RetDemo5.Retval) list.get(i);
System.out.println("count = " + ret.count +
" sum = " + ret.sum + " mean = " + ret.mean);
}
}
}

getStats 가 호출 될 때마다, 데이터가 테이블화되고 Retval 객체가 생성되어, Retval 레코드의 리스트에 추가됩니다.

메소드에서 여러개의 값 리턴하기에 대해 더 상세한 정보를 원한다면
Arnold, Gosling, Holmes 공저 「 The Java Programming Language Third Edition」(http://java.sun.com/docs/books /javaprog/thirdedition/)의
섹션 2.6. 4 「Parameter Values」를 보시기 바랍니다.
반응형