좀 더.. 누구나 초보도 이해하기 쉽게 계속 추가할것이다. 적는 사람도 뭐 그리 잘난 사람 아니다 :) 


2016-12-12 

자.. 맵에 맵을 넣어서 어딘가로 넘겨서 그 맵안에 맵을 수정하는게 되느냐 안되느냐? 당연히 된다.

그런데 참조형태로 안되는 케이스가 보인다면 ... ?? (참고로 맵안의 맵이라고 했지만 그냥 맵도 상관없다;;; 그냥 참조연산의 깊이에 대한 예도 들겸  예로 든것 뿐이다)


0. 자바는 C 의 표현으로 따지자면 call by value 이다. 즉 기본적으로 레퍼런스 연산이 아니다.

1. 맵안의 맵의 값을 고치겠다고 하는 행위가 맵안의 맵에 다른맵을 대입했다

--> 안된다. call by value 오케? 그러나... 클래스 자체를 넘기는.. 경우는 레퍼런스의 주소를 값으로 넘기는 형태이므로 참조를 가져올 수 있고 수정이 원래 된다. 그럼 왜 맵안의 맵에 새로 만든 인스턴스를 대입해서 수정하는건 안되느냐고? 간단하다. 왼쪽에서 오른쪽을 기억해라..  맵안의맵에 다른 새로생성한 객체를 대입하는건 단지 맵안의맵이 가진 주소를 담고 있던곳이 새로운 인스턴스를 보게 한것에 불과하다. 즉 원래 맵안의맵이 가리키던 주소의 값은 변화가 없다.


2. 그럼 좋아. 맵안의맵을 new 로 새로인스턴스화한다음 뭔가 했는데... 안된다!?

--> 안된다. 왼쪽에서 오른쪽... 맵안의 맵에는 그 참조하고자하는 값을 참조하고 있는게 아니라. 단지 그 참조하고자하 하는 주소의 정보를 가지고 있는것에 불과하다.즉 직접 참조하는게 아니다! 해서 원래 참조하고자하는 곳의 위치값을 저장하고 있을뿐인 곳에 새로운 인스턴스를 대입하는건, 단지 맵안의맵에 들어있는 원래참조하고자 하는 위치 ..레퍼정보의 값이 있는곳을 새로운 인스턴스의 레퍼위치정보 값을 보도록 수정하는것에 불과하다. 즉 오리지널은 여전히 잘 존재하고 있고 그 오리지널의 레퍼위치정보를 담고 있던 곳(직접참조가 아니다!! 그냥 그 위치를 기록한 새로운 장소일뿐이다)이 새로운곳을 바라보게 한것에 불과하다.


그럼 어떻게 수정하냐고? 간단하다. 그냥 꺼내서 그 자체로 수정하몀된다. 뭔가 대입하거나 새로 new 인스턴스화하지 말고 그 맵자체에 put 을 하든 remove 를 하든 그안에값을꺼내서 add 를 하든 하면 된다.


뭔가 표현이 어려웠는데 간단히... 프리미티브가 아닌 타입(클래스)를 어떤 함수에서 받았을때, 그게 맵이고 그 맵안에 여러개의 맵이 있을경우... 혹은 그 맵자체도 마찬가지임. 맵안의맵=새맵 또는 맵안의맵 = new ...  는 원본에 대한 수정이 일어나지 않는다.


자바는 call by value. 즉 위치정보값 을 넘겨주는 것이지 위치를 넘겨주는게 아니란 소리다. 위치(레퍼)정보값을 새로운상자에 담아서 넘기는것이다. 해서 그 상자에 새로운걸 담는다고(대입이나 new) 원본이 바뀌는게 아니다!!!

위치정보값을 넘겨주기에 참조형태로 수정은 할 수 있으나 대입이나 new 를 해버리면 왼쪽에서 오른쪽... 참조할대상의위치정보값을 가진 곳이 새로운 곳을 보는것이지,, 원래 참조대상에 대한 수정이 일어나는게 아니란건다.

해서 위의 행위를 제대로 하려면 mapinmap.clear(); 뒤에 원하는값을 put 해라. (clear 하는 이유는 다지울필요가 있을때 하란소리다) 


2016-12-12 

http://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value

위의 글중

어떤 함수로 넘겨받은 참조값(정확히 주소라고할 순 없을것 같다) ... 을 새로운 변수명에 할당한(즉 이게 파라미터로 받는행위)상태에서 거기에 new 를 한다는것은 그 변수에 그 참조(주소)를 넣는게 아니라 그 변수를 새로운 참조로 바라보게 하는것이다. 왼쪽에서 오른쪽...

해서 원본은 손상되지 못하는것이다. 그 파라미터 변수는 원본 그 자체가 아니라 원본의 참조(주소)를 담고 있는(값복사) 상태였다가 new 를 통해서 그 새로운 주소를 보게 되는거지 원본에 새로운 주소를 덮어씌우는게 아니기 때문이다. 


I just noticed you referenced my article.

The Java Spec says that everything in Java is pass-by-value. There is no such thing as "pass-by-reference" in Java.

The key to understanding this is that something like

Dog myDog;

is not a Dog; it's actually a pointer to a Dog.

What that means, is when you have

Dog myDog = new Dog("Rover");
foo(myDog);

you're essentially passing the address of the created Dog object to the foo method.

(I say essentially because Java pointers aren't direct addresses, but it's easiest to think of them that way)

Suppose the Dog object resides at memory address 42. This means we pass 42 to the method.

if the Method were defined as

public void foo(Dog someDog) {
    someDog.setName("Max");     // AAA
    someDog = new Dog("Fifi");  // BBB
    someDog.setName("Rowlf");   // CCC
}

let's look at what's happening.

  • the parameter someDog is set to the value 42
  • at line "AAA"
    • someDog is followed to the Dog it points to (the Dog object at address 42)
    • that Dog (the one at address 42) is asked to change his name to Max
  • at line "BBB"
    • a new Dog is created. Let's say he's at address 74
    • we assign the parameter someDog to 74 
  • at line "CCC"
    • someDog is followed to the Dog it points to (the Dog object at address 74)
    • that Dog (the one at address 74) is asked to change his name to Rowlf
  • then, we return

Now let's think about what happens outside the method:

Did myDog change?

There's the key.

Keeping in mind that myDog is a pointer, and not an actual Dog, the answer is NO. myDog still has the value 42; it's still pointing to the original Dog (but note that because of line "AAA", its name is now "Max" - still the same Dog; myDog's value has not changed.)

It's perfectly valid to follow an address and change what's at the end of it; that does not change the variable, however.

Java works exactly like C. You can assign a pointer, pass the pointer to a method, follow the pointer in the method and change the data that was pointed to. However, you cannot change where that pointer points.

In C++, Ada, Pascal and other languages that support pass-by-reference, you can actually change the variable that was passed.

If Java had pass-by-reference semantics, the foo method we defined above would have changed where myDog was pointing when it assigned someDog on line BBB.

Think of reference parameters as being aliases for the variable passed in. When that alias is assigned, so is the variable that was passed in.






from http://mussebio.blogspot.kr/2012/05/java-call-by-valuereference.html

정리가 잘 된 글로 보인다!!



2012년 5월 9일 수요일

Java 인자 전달 방식: Call-by-{Value | Reference}?


[이미지 출처 - Google]
C++를 마지막으로 사용해본지 거의 6년이 다 되어가는 지금 Java를 다루면서 잊고 있었던 "Parameter passing in Java: call-by-value vs call-by-reference?"란 주제가 기본을 일깨워 주는 좋은 양식이 되겠다 싶어 내용을 정리해 보려고 합니다.

이 주제는 지금까지 다양한 커뮤니티상에서 오랫동안 논의 되었고, 그로 인해 잘 정리된 문서와 사이트를 어렵지 않게 찾을 수 있어 최대한 많은 참조를 토대로 작성하고자 합니다.

Java 개발자로서 두 매커니즘의 차이에 혼돈을 일으키는 이유는 아마도 
  • 서로 다른 인자전달 매커니즘을 가지는 언어를 두루 사용해 본 경험이 없거나,
    • 이 경우는 왜 이 문제가 그토록 많이 논의되었는지 잘 이해하지 못함
  • 경험이 있다 하더라도 정확한 이해없이 흘려버린 경우
등이 있지않나 생각됩니다.(고슬링 옹이 직접 시원하게 말해줬음 좋겠구만 @^^@)

0. 사전지식(Parameter, Argument, Syntax/Semantics, Reference)

메서드나 함수의 인자 전달을 설명 할 때 항상 등장하는 단어가 parameter와 argument입니다. 두 단어 모두 인자를 표현하는 단어지만 구분해서 쓰자면 formal-parameter(형식인자)와 actual-parameter(실인자)로 이해하는 것이 적당합니다. 다음 코드에서 parameter와 argument의 차이를 간단히 보도록 하겠습니다.
01public class WhatIsParameter {
02    //int형 변수 something은 formal-parameter
03    public static void doSomething(int something) {
04        System.out.println("something is " + something);
05    }
06    public static void main(String[] args) {
07        int ten = 10;
08        doSomething(ten); //ten의 값 10은 actual-parameter
09        System.exit(0);
10    }
11}
Syntax와 semantics는 여기서 간단히 다룰 주제는 아니지만 이해를 돕기위한 것이니 아래 코드 정도만 두 용어로 이해해 봅니다. 
1//Case1.
2int primitiveIntVar = 1024;
3//Case2.
4String whoAreYou = "It's me! Uncle Bob";
두 문장은 모두 올바른 Java 문법으로 작성되었습니다. Syntax는 말 그대로 언어의 구문규칙을 의미합니다. 그럼 semantics는 뭘까요? 그대로 해석하면 "의미"인데 누구한테 가치가 있는 "의미"일까요? 언어의 semantics는 컴파일러가 구문(syntax)을 분석한 뒤 해석하여 실제로 동작에 사용되는 명령어를 생산해 내는데 사용되는 지침(모델)입니다.

Case1과 Case2의 syntax는 거의 비슷하지만 semantics는 완전히 다릅니다. Case1은 "정수형 변수 primitiveIntVar에 정수 1024를 저장"하는 명령어를 생성해 내라는 의미라면, Case2는 "문자열 객체 It's me! Uncle Bob을 생성하고, String 타입의 변수 whoAreYou에 생성된 객체의 레퍼런스를 저장"하는 명령어를 만들어 내시오라는 의미를 가지고 있습니다. 

여기서 한 가지 기억해야 할 점은, primitiveIntVar 변수의 값이 1024인 반면, whoAreYou 참조변수의 값은 "It's me! Uncle Bob"이 아닌 이 객체를 참조 할 수 있는 포인터(pointers) 값 즉, 레퍼런스란 사실입니다.(다음은 Java(SE7) Language Spec.의 4.3.1 Object 파트의 일부 내용입니다.)

"...... The reference values(often just references) are pointers to these objects, and a special null reference, which refers to no object. ......"

요약하면, 메서드의 인자로 값을 전달하는 Java 언어의 매커니즘이 call-by-value semantic인가 call-by-reference semantic인가를 알아보는 것이 본 포스트의 목적입니다.(아..서론이 너무 길었나 ㅠㅠ)

1. 인자전달 방식


call-by-value와 call-by-reference semantic의 정의를 알아보기 위해 Wikipedia를 방문했습니다.(두 semantics의 정의안에 "오해의 소지"와 "정답의 근거"가 있다고 판단되기에 짚고 넘어갑니다.)
Call-by-value
In call-by-value, the argument expression is evaluated, and the resulting value is bound to the corresponding variable in the function (frequently by copying the value into a new memory region). If the function or procedure is able to assign values to its parameters, only its local copy is assigned — that is, anything passed into a function call is unchanged in the caller's scope when the function returns.
위 내용에 따르면, call-by-value 매커니즘은 함수로 인자를 전달할 때 전달 될 argument expression의 결과(actual-parameter)를 대응되는 함수의 변수(formal-parameter)로 복사하며, 복사된 값은 함수내에서 지역적으로 사용되는 local value라는 특징을 가지고 있습니다. 그리고 caller는 인자값을 복사 방식으로 넘겨 주었으므로, callee 내에서 어떤 작업을 하더라도 caller는 영향을 받지 않습니다. Call-by-value의 대표적인 예제 코드가 C언어에서 포인터를 설명할 때 등장하는 swap 함수입니다.
01void swap(int first, int second) {
02    int tmp = first;
03    first = second;
04    second = tmp;
05}
06int main(int argc, char** argv) {
07    int x = 10, y = 20;
08    swap(x, y);
09    printf("x = %d, y = %d\n", x, y);
10    return 0;
11}
x = 10, y = 20

위 코드에서 main 함수가 swap 함수를 호출할 때 swap 함수가 필요로 하는 x와 y의 값을 먼저 계산하고 그 값(10과 20)을 swap 함수로 복사해서 넘겨줍니다. swap 함수는 함수가 실행 될 때 스택에 first, second, tmp 세 임시 저장공간을 만들었다가 전달 받은 10과 20을 각각 first와 second에 저장한 후 함수내 코드를 실행합니다. swap 함수는 종료할 때 임시로 생성했던 모든 공간을 삭제하므로 first, second, tmp로 작업했던 내용은 모두 사라지게 되고, main 함수의 x와 y는 그대로 10과 20을 유지하고 있습니다. 요약하면 first와 second 그리고 x와 y는 아무런 관련이 없는 독립적인 변수들입니다.

그럼 call-by-reference에 대한 정의를 살펴보도록 합시다. 
Call-by-reference
In call-by-reference evaluation(also referred to as pass-by-reference), a function receives an implicit reference to a variable used as argument, rather than a copy of its value. This typically means that the function can modify (i.e. assign to) the variable used as argument—something that will be seen by its caller.
Call-by-reference는 인자로 사용될 변수의 묵시적인 레퍼런스를 함수로 전달하며, 그것이 변수의 값은 아니다라고 되있습니다. 이해를 돕기 위해 이전 swap 함수의 예를 다시 보면, 만약 call-by-reference로 swap 함수에 뭔가를 전달한다면 x와 y가 가지고 있는 값(10, 20)이 아닌 x, y 변수자체에 대한 레퍼런스를 전달해야 합니다. C언어는 포인터로 call-by-reference를 구현할 수 있습니다.
01void swap(int* first, int* second) {
02    int tmp = *first;
03    *first = *second;
04    *second = tmp;
05}
06int main(int argc, char** argv) {
07    int x = 10, y = 20;
08    swap(&x, &y);
09    printf("x = %d, y = %d\n", x, y);
10    return 0;
11}
x = 20, y = 10
위 코드에서는 x와 y가 가진 값을 swap 함수로 전달 한 것이 아닌, x와 y 자체의 레퍼런스(주소 값)를 전달했습니다. 마지막으로 다음 C코드를 통해 두 내용을 정리해 봅시다. 아래 코드에서 (1)(2)(3)은 각각 call-by-value/call-by-reference일까요?
1void modify(int p, int* q, int* r) {
2    p = 27;  //(1)
3    *q = 27//(2)
4    *r = 27//(3)
5}
해답은 (1)의 경우, call-by-value이고, (2)(3)은 함수를 호출한 caller가 어떤 값을 actual-parameter로 넘겨주었는지 확인해 보아야 알 수 있습니다. 만약 caller가 다음과 같다면 
1int main(int argc, char** argv) {
2    int a = 1;
3    int b = 1;
4    int x = 1;
5    int* c = &x;
6    modify(a, &b, c);   // a is passed by value, b is passed by reference by creating a pointer,
7                        // c is a pointer passed by value
8    return 0;
9}
b는 call-by-reference로 전달된 것이고, c는 call-by-value로 전달된 것입니다. 단 c의 경우 c 자체의 레퍼런스가 아닌 c가 저장하고 있는 값(x에 대한 레퍼런스 값)을 넘겨준 경우입니다. 결과적으로 함수호출 후 caller에서 변경되는 변수는 b와 x 두 개입니다. 

2. 오해의 소지


Java의 경우를 살펴봅시다. 우선 Java의 메서드 인자전달 방식이 무엇인가를 판단하기 위해 두 매커니즘의 가장 큰 특징을 위주로 검증해 보겠습니다. Call-by-value의 특징은 
  • Actual-parameter 의 값을 복사하여 전달하므로 caller는 callee의 작업(?)에 영향을 받지 않는다!!!
입니다. 조금 느슨하게 적은 감이 팍 오지만 다음 코드로 위 내용을 살펴봅시다. 테스트용으로 사용할 Person 클래스입니다.
01public class Person {
02    private String mName;
03     
04    public Person(String name) {
05        mName = name;
06    }
07    public void setName(String name) {
08        mName = name;
09    }
10    @Override
11    public String toString() {
12        return "Person " + mName;
13    }
14}
다음은 Person 객체를 넘겨받아 새로운 Person 객체로 변경하는 메서드 호출 코드입니다.
01public class CallyByXXXTest {
02    public static void assignNewPerson(Person p) {
03        p = new Person("Bob");
04    }
05    public static void main(String[] args) {
06        Person sam = new Person("Sam");
07        assignNewPerson(sam);
08        System.out.println(sam);
09        System.exit(0);
10    }
11}
Person sam
sam 객체를 생성한 후 assignNewPerson 메서드로 전달하고 메서드 종료 후 sam 객체가 bob 객체로 변경되었는지 확인해 봤습니다. 결과는 아무런 변화가 없습니다. 그럼 call-by-value semantic일까요? 다음 코드를 봅시다. 
01public static void changePersonName(Person p) {
02    p.setName("Bob");
03}
04public static void main(String[] args) {
05    Person sam = new Person("Sam");
06    changePersonName(sam);
07    System.out.println(sam);
08     
09    System.exit(0);
10}
Person bob
sam 객체의 mName 필드를 변경하는 changePersonName 메서드를 실행한 후 결과를 확인해 보니 변경사항이 발생했습니다. 오해의 소지는 여기서 그리고 용어에서 발생합니다.
  1. 특정 메서드 내에서 전달 받은 객체의 상태를 변경 할 수 있음. 
  2. 참조변수는 임의의 객체에 대한 레퍼런스를 저장하므로 메서드로 전달한 값이 레퍼런스(call-by-reference?)
1의 경우, sam 참조변수가 가리키는  [이름 속성이 "Sam"인 Person 객체]를 [이름 속성이 "Bob"인 새로운 Person 객체]로 변경한 것이 아니라, 단지 이름 속성만 변경했을 뿐입니다.

2의 경우, 전달 된 레퍼런스는 참조변수 sam 자체의 레퍼런스(주소값?)가 아닌 sam이 저장하고 있는 값(이것도 레퍼런스)입니다.
    만약 Java가 call-by-reference 매커니즘을 지원한다면 참조변수 sam 자체의 레퍼런스(주소)를 얻을 수 있는 방법이 있어야 합니다. 그러나 Java는 이 방법을 지원하지 않습니다. 따라서 "참조변수 sam에 저장된 값(다른 객체의 레퍼런스 값)"을 복사하여 formal-parameter p로 넘겨준 call-by-value 매커니즘이 합당해 보입니다.

3. Java를 왜 이렇게 설계했을까?

다음 인용문을 통해 이 질문을 정리할까 합니다.
It seems that the designers of Java wanted to make sure nobody confused their object pointers with the evil manipulable pointers of C and C++, so they decided to call them references instead. So Java books usually say that objects are stored as references, leading people to think that objects are passed by reference. They’re not, they’re passed by value, it’s just that the value [on the stack at JVM level] is a pointer.
4. 결론
  • 두 매커니즘의 정리
    • passing value of actual-parameter variable : call-by-value
    • passing reference of actual-parameter variable : call-by-reference
  • Java의 메서드 인자전달 방식은 call-by-value
    • value란? 객체에 대한 포인터 값(레퍼런스) 또는 primitive 타입의 값



+ Recent posts