자바는 call by value이다. 


어떤 서브루틴이의 인자에 대한 변경이 원래 변수의 값에 영향을 미칠 때 call by reference라고 한다. 포인터를 이용하면 주소 값을 전달해주기 때문에 call by reference를 흉내 낼 수는 있지만 call by reference는 아니다. 
포인터를 사용한 call by reference를 흉내 낸 경우 호출된 함수의 스택에 넘겨받은 주소를 보관하기 위한 로컬 변수가 생성된다. 하지만, call by reference를 지원하는 언어의 경우 스택에 넘겨받은 주소를 보관하지 않는다. 
call by reference를 지원하는 언어로 C++의 참조형연산자(&)와 FORTRAN등이 있다.
참조형과 포인터 변수와의 차이점은 주어진 주소를 변경할 수 있느냐이다


참조형 변수를 한번 선언하면 가리키고 있는 기억 장소에 대한 주소를 변경할 수 없다.!!!!


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package callbyvalue;
 
public class Start4 {
    public static void main(String[] args) {
        int k =5;
        int m;
 
        m = square(k);
    }
    private static int square(int k){
        int result;
        k=25;
 
        result = k;
 
        return result;
    }
}
 
cs

메서드 지역변수끼리 참조 불가인 이유


1. 메서드는 서로의 고유의 공간인데, 서로 침범하면 무단 침입으로 자바 월드에 문제를 유발하기 때문에

2. 포인터 문제이다. sqaur()메서드에서 main()메서드의 내부의 지역변수 m에 접근한다고 하면 m의 위치를 명확히 알아야 하는데, 그 위치를 명확히 알기 위해서는 바로 m 변수의 메모리 위치, 즉 포인터라고 읽고 메모리 주소 값이라 이해해야 하는 그 값을 알아야 한다. 근데 자바는 그게 없다. 




call by value와 call by reference

오늘 내용은 제목에도 있다시피 java는 call by value?call by reference? 를 알아볼 예정이다. 그 전에 아주 간단하게 call by value와 call by reference에 대해서 살짝 살펴보자. 보통 c 보다 자바를 먼저 공부한 개발자는 들어본 개발자도 있을 것이고 듣지 못한 개발자도 있을 수 있다. 왜냐하면 책에서 딱히 다루지 않기 때문이다. 필자도 자바책에서는 거의 못본.. 사실 자바만 다루는책은 두권정도밖에 없다. 그래서 못본거일 수도.. 하지만 c와 c++을 먼저 공부를 했다면 무조건 등장한다. 잠시 c언어 책을 꺼내고 살펴보도록 하자.

call by value (값에 의한 호출)

call by value는 가장 일반적인 함수 호출형태로 값을 복사하는 것이다. 예를 들어 보자.

int add(int a, int b) 
{
    return a + b;
}

c 의 함수의 기본적인 형태이다. (혹시 다음에 나올 문법이 c++이라도 그냥 c라고 이야기 하겠다.) add 메서드를 호출하면 a + b를 더한 값을 리턴한다. 호출 해보자.

int a1 = 10;
int a2 = 20;
cout << add(a1, a2) << endl;

값은 당연히 30이 출력 될 것이다. 여기서 변수 a1과 a2는 add() 함수의 a, b와는 완전 별개의 변수가 된다. 즉 값이 복사되는 것이다. 그래서 만약 add 함수의 a와 b를 변경 하더라도 a1와 a2는 영향을 받지 않는다. 뭐 당연한 말을 이렇게 하나 싶은데 좀 더 살펴보자.

call by value와 call by reference가 나오면 단골로(?) 나오는 예제를 살펴보자. 예상했겠지만 swap함수를 만들어보자.

void swap(int a, int b)
{
    int temp = a;
    a = b;
    b = temp;
}

뭐 어려운 부분은 없다. 두 변수를 바꾸는 함수이다. 한번 호출해보자.

int a1 = 10;
int a2 = 20;
swap(a1, a2);
cout << "a1: " << a1 << " a2: " << a2 << endl;

출력 해보면 a1: 10 a2: 20 이와 같이 출력 될 것이다. 아까 위에서 a1과 a는 별개의 변수인 것이 증명 되었다. 뭔 당연한 소리를 이렇게 길게 이야기 하고 있어? 라고 할 수도 있으니 빨리 다음으로 넘어가자.

call by reference (참조의 의한 호출)

call by reference 를 자세히 이야기하면 글이 너무 길어지므로 간단하게만 이야기 해보자. 포인터에 대해서도 잘알고 해야되는데 여기에서는 그게 중요한 내용이 아니므로 모르는 사람이 있다면 그냥 이런게 있다고만 알고 있자.

간단하게 한줄로 요약하자면 변수의 주소를 전달하는 것이다. 다시 swap 함수를 call by reference 로 만들어보자.

void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

int a1 = 10;
int a2 = 20;
swap(&a1, &a2);
cout << "a1: " << a1 << " a2: " << a2 << endl;

호출하면 아까 위의 call by value 와는 다르게 a1: 20 a2: 10 두개의 변수가 바뀌어 출력 된다. 이게 바로 call by reference 참조의 의한 호출이다. 포인터 변수 a와 b에는 각각의 a1의 주소 a2의 주소가 전달된다. call by value 처럼 값을 복사하는 것이 아니고 실제 주소를 전달하여 swap 함수 내에서 조작을 하면 실제 값도 변경 될 수 있는 것이다.

이정도면 아주 정확히는 몰라도 어느정도 두개의 차이점을 알아봤다. 그렇다면 과연 java는 call by reference 일까?

java는 call by reference가 아니다.

결론부터 말하자면 java는 항상 call by value이다. 흔히 java의 오해를 살 수 있는 부분을 살펴보자.

public class CallByValue {

  public static void main(String[] args) {
    Person p = new Person("wonwoo");
    System.out.println("p.name: " + p.name);
    callByValue(p);
    System.out.println("p.name: " + p.name);
  }

  public static void callByValue(Person p) {
    p.name = "kevin";
  }
}

class Person  {
  String name;

  public Person(String name) {
    this.name = name;
  }
}

위의 코드를 출력해보면 아래와 같이 출력 된다.

p.name: wonwoo
p.name: kevin

읭? 바뀌었는데? 그럼 자바도 call by reference가 아닐까? 오해의 소지는 여기서 발생했다. 실제 상태값을 바꾸는 것에서 오해가 시작되었다. call by reference라면 상태를 변경하는게 아니라 실제 callByValue 함수의 p에 다른 Person 객체를 넣어 바뀐다면 그게 call by reference가 되는 것이다. 다음과 같이 말이다. ===> 참조하는 값이 바뀐것(p.name="kevin"으로 변화)이기 때문에 가리키는 것이 wonwoo였다가 kevin으로 덮여씌이면서 바뀌게 됨

public class CallByValue {

  public static void main(String[] args) {
    Person p = new Person("wonwoo");
    System.out.println("p.name: " + p.name);
    callByValue(p);
    System.out.println("p.name: " + p.name);
  }

  public static void callByValue(Person p) {
    p = new Person("kevin");
  }
}

class Person  {
  String name;

  public Person(String name) {
    this.name = name;
  }
}

callByValue 메서드부분만 바뀌었다. callByValue 메서드를 보면 이름을 kevin으로 다시 생성해서 할당한다. 만약 자바가 call by reference라면 아까와 동일하게 출력 되어야만 한다. 하지만 이 코드를 출력해보면 다음과 같다.

p.name: wonwoo
p.name: wonwoo

실제로 Person은 변경되지 않았다. call by reference가 아닌 또다른 이유는 자바에서는 객체의 주소를 가져오는 방법이 없다. 만약 call by reference 지원한다면 주소를 가져오는 방법을 지원해야 할 것인데 말이다. ==> 참조하는 값 즉, 가리키는 값은 wonwoo로 같은데 힙영역에 객체를 하나 더 생성해서 wonwoo, kevin 두개 생겼으나 지금 가리키는 값은 위에서 p.name  wonwoo가리키는것으로 동일해서 wonwoo출력 만약에 kevin하고 싶으면 위에서 다시  p = new Person("kevin")하고 p.name = "kevin"해도 그대로 wonwoo출력  그니까 객체가 지금 2개나 생성됬는데 주소를 모르니까 위에 wonwoo가리키는거 그대로 가리키게됨 -> 참조형 변수를 한번 선언하면 가리키고 있는 기억 장소에 대한 주소를 변경할 수 없다.!!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class CallByReference {
 
    public static void main(String[] args) {
        Person p = new Person("wonwoo");
        System.out.println("p.name: " + p.name);
        callByReference(p); //여기서 p는 위에있는 Person p = new Person("wonwoo") 이 객체 만들었던거의 참조변수임 
        // 그래서 가리키는게 동일 아무리 밑에 메서드에서 바꿔줘도 차라리 가리키는걸(p.name = "kevin") 바꿔줘야지 
        // 객체 하나 더 생성(p = new Person("kevin")한다고 해서 바뀌는게 아님
        System.out.println("p.name: " + p.name);
    }
 
    public static void callByReference(Person p) {
        p = new Person("kevin"); //객체 옆에 또 생성
        //p.name = "kevin"; 해도 그대로 wonwoo출력
    }
}
 
class Person  {
    String name;
 
    public Person(String name) {
        this.name = name;
    }
}
cs


그럼 우리는 call by reference가 어떤건지 보자. c는 call by reference 를 지원하니 c코드를 보자. 정확히는 c++.. 오랜만에 c로 코딩할려니 문법조차 다 까먹었다.. Xcode도 거의 4년? 5년만에 열었..

#include <iostream>

using namespace std;

class Person 
{

    public :
    string name;

    Person (string name) {
        this->name = name;
    }
};

int add(int a, int b)
{
    return a + b;
}

void callByReference(Person *p)
{
    *p = Person("kevin");
}

void callByValue(Person p)
{
    p = Person("kevin");
}

int main(int argc, const char * argv[]) 
{
    Person p("wonwoo");
    cout << "p.name: " << p.name << endl;
    callByReference(&p);
//    callByValue(p);
    cout << "p.name: " << p.name << endl;
    return 0;
}

아까 자바와 동일하게 만들었다. 실제 callByReference() 함수를 호출하면 실제 바뀐 내용이 출력 되지만 callByValue() 함수를 호출할 경우에는 자바와 동일하게 wonwoo가 두 번 출력 된다. 이로써 java는 call by reference가 아니라는게 증명? 되었을 것이라고 생각된다.


http://wonwoo.ml/index.php/post/1679



자바는 기본 자료형에 해당하는 변수를 인자로 넘길때는 값을 넘기고, 참조자료형인 변수를 인자로 넘길 때는 레퍼런스를 넘기는 것 처럼 보이게 되어있다.


정확히 말하면 변수가 레퍼런스 타입의 경우에는 그 변수가 가지는 값이 레퍼런스(주소 값)이므로 call by value에 의해 레퍼런스가 전달되는 것이다.

그러니까 value란 객체에 대한 포인터 값(레퍼런스) 또는 primitive 타입의 값인 것이다. 이렇듯 자바는 객체의 타입에 따라 값만 넘기는 것 같기도 하고, 레퍼런스를 넘기는 것 같기

도 합니다. - https://brunch.co.kr/@kd4/2


1
2
3
4
5
6
7
8
9
10
11
12
13
14
package callbyvalue;
 
public class CallByValue {
    public static void main(String[] args) {
        int a = 10;
        int b  = a;
 
        b = 20;
 
        System.out.println(a);
        System.out.println(b);
    }
}
 
cs




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package callbyvalue;
 
public class CallByReference {
    public static void main(String[] args) {
        Animal ref_a = new Animal();
        Animal ref_b = ref_a;
 
        ref_a.age = 10;
        ref_b.age = 20;
 
        System.out.println(ref_a); //객체의 값 자체는 같은 값이 나옴
        System.out.println(ref_b); //객체의 값 자체는 같은 값이 나옴
        System.out.println(ref_a.age); //객체의 참조 값은 다르게 나옴 객체를 생성해서 나온 참조 변수가 가리키는 객체는 Animal객체로 동일하기 때문에 ref_a.age=10했지만 
        //그 위에 다시 가리키는 객체 참조가 같았던Animal ref_b = ref_a;라서 ref_b.age = 20; 이이서 20으로 덮여씌여짐 그래서 10이 아닌 20
        System.out.println(ref_b.age); //Animal 객체를 ref_b또한 똑같이 가리키고 있으니까 힙 영역의 인스턴스에서 저장된 age값인 20이 출력됨
 
    }
}
 
//이것은 static 클래스도 static 메서드도 아니기떄문에 객체 생성없이 만들어 질 수 없다 그래서 객체를 항상 생성해주는것이었다.
//그래서 클래스의 인스턴스화를 해서 클래스를 객체 처럼 사용하는 것이었다.
class Animal{
    public int age;
}
 
cs

결국 call by value와 call by reference를 다르다고 이해하기 보다는 기본 자료형 변수는 저장하고 있는 값을 그 자체로 판단하고, 참조 변수는 저장하고 있는 값을 주소로 판단한다고 이해하는 것이 더 쉽다. - 스프링 입문을 위한 자바 객체지향의 원리와 이해






"자바는 call by value 입니다."


자바는 객체를 가르키는 레퍼런스라는 개념이 있는것이지, 이게 함수의 매개변수로 넘어갈때는 

call by value 로 넘어갑니다. 즉  레퍼런스 그 자체가 deep copy 되어 넘어갑니다.


언어별 정리


C       :  call by value   ( 포인터 그 자체도 매개변수로 넘어갈때 value  복사됨, clone copy 됨 ) 

C++   : call by value + call by reference   (  "&  레퍼런스"  일 경우 call by reference , 즉 clone copy 하지 않음 )

Java  : call by value   ( 레퍼런스 그 자체도 매개변수로 넘어갈때 value  복사됨, , clone copy 됨 )  => 주소값을 알 수 없을뿐더러 한번 생성하면 주소값이 변하지 않음 -> 객체가 가리키는 주소가 변하지 않음 참조형 변수를 한번 선언하면 가리키고 있는 기억 장소에 대한 주소를 변경할 수 없다.!!!!

C#     : call by value + call by reference  ( "ref"  키워드 붙으면  call by reference , 즉 clone copy 하지 않음 )

Python : call by value ( python은 모두 객체이기때문에, call by object 라 불리지만, 이 글에서는 이해를쉽게하고자  저렇게 말함, 즉 자바의 객체 레퍼런스를 넘기는것과 동일 효과) 

Javascript : call by value ( 자바,파이썬과  동일) 



출처: https://hamait.tistory.com/468 [HAMA 블로그]




'자바 이론 > C++언어 vs JAVA언어 차이' 카테고리의 다른 글

return 의 쓰임  (0) 2019.02.08
<vector>쓰임법  (0) 2019.02.06

https://kldp.org/node/18751


일반적으로 함수가 의미하는 바에 따라서 리턴값이 달라집니다.

1. boolean계열의 의미를 지니는 경우는 1일 TRUE이고 0이 FALSE인 경우가 많습니다.

2. 여러가지 integer값을 반환하는 경우는 0이 TRUE음수가 FALSE양수가 특정 상태를 반환하는경우가 종종 있습니다.

일반적인 경우입니다. memcmp()나 isdigit()같이 비교해보면 쉽게 이해가 가실런지?



함수에서와 같이 프로그램에서도 리턴값이 있습니다.
함수는 넘긴 main에서 값을 넘길때는 Argument라고 하고
함수에서 그 값을 받아 쓸때는 Parameter라고 하지요?
이 값을 받아서 계산하고 그 결과를 return합니다.
이 경우 두가지를 생각 해 볼 수 있는데

어떤 값을 리턴하는 것과 참 거짓 여부를 리턴 하는 것입니다.
값을 리턴 하는 것은 쉽게 생각 하실 수 있을테고
참/거짓 여부를 리턴 하는 경우 대게 거짓은 0, 참은 1을 리턴하지요
이것을 main에서는 for, while문 루프나 if 판단문에서 사용 할 수 있겠죠.

그런데 프로그램의 경우는 약간 다릅니다.
for, while, if 에서는 0을 거짓으로 보았으나 프로그램의 경우에는
0을 리턴하면 정상적인 종료, 그 이외의 값은 잘못된 종료로 생각하기 때문에
shell script를 짤때 이를 이용하기도 하죠.

함수의 경우와 프로그램의 경우에서의 차이점을 알고 쓰심이 좋을것같습니다.
이 부분을 지적한 글이 없는것같아 몇자 적어봅니다.
^^ 수고하세여..


+ Recent posts