[Java] 자바는 "참조에 의한 전달"인가 "가치에 의한 전달"인가?



Answers

나는 당신이 나의 기사 를 참조하는 것을 보았습니다.

Java Spec은 Java의 모든 것이 가치에 의해 전달된다고 말합니다. 자바에서는 "참조에 의한 통과"와 같은 것이 없다.

이것을 이해하는 열쇠는

Dog myDog;

개가 아닙니다 . 실제로는 Dog에 대한 포인터 입니다.

그것이 의미하는 바는 당신이 가질 때입니다.

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

기본적으로 생성 된 Dog 객체의 주소foo 메서드에 전달합니다.

(필자는 자바 포인터가 직접 주소가 아니기 때문에 본질적으로 말하지만, 그렇게 생각하는 것이 가장 쉽다)

Dog 객체가 메모리 주소 42에 상주한다고 가정합니다. 즉, 메소드에 42를 전달합니다.

Method가로 정의 된 경우

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

무슨 일이 일어나는지 보도록하겠습니다.

  • 매개 변수 someDog 가 값 42로 설정됩니다.
  • "AAA"라인에서
    • someDog 는 그것이 가리키는 Dog (주소 42의 Dog 객체)
    • Dog (42 번지에있는 개)는 그의 이름을 맥스로 바꿀 것을 요청받습니다.
  • 라인 "BBB"
    • 새로운 Dog 만들어집니다. 그가 74 번지에 있다고 가정 해 봅시다.
    • 파라미터 someDog 를 74에 대입합니다.
  • 라인 "CCC"
    • someDog가 가리키는 Dog (주소 74의 Dog 객체)
    • Dog (주소 74에있는 Dog )는 그의 이름을 Rowlf로 바꿀 것을 요청받습니다.
  • 그 다음, 우리는 돌아 간다.

이제 메소드 밖에서 일어나는 일에 대해 생각해 봅시다.

myDog 바뀌 었습니까?

열쇠가 있습니다.

myDog 는 실제 Dog 가 아니라 포인터 임을 명심하십시오. 대답은 NO입니다. myDog 의 값은 여전히 ​​42입니다. 여전히 원래 Dog 가리키고 있습니다 (단, 줄 "AAA"로 인해 그 이름은 "Max"임 - 여전히 동일한 Dog, myDog 의 값은 변경되지 않았습니다).

그것은 주소를 따르고 그것의 끝에 무엇이 바뀌는 것이 완벽하게 유효합니다; 그러나 변수를 변경하지는 않습니다.

Java는 C와 똑같이 작동합니다. 포인터를 지정하고 메소드에 포인터를 전달하고 메소드의 포인터를 따라 가며 가리킨 데이터를 변경할 수 있습니다. 그러나 포인터가 가리키는 위치는 변경할 수 없습니다.

C ++, Ada, Pascal 및 참조로 전달을 지원하는 다른 언어에서 실제로 전달 된 변수를 변경할 수 있습니다.

Java가 pass-by-reference 의미론을 가지고 있다면 위에서 정의한 foo 메소드는 myDog 이 BBB 라인에서 someDog 를 할당 할 때 가리키고있는 곳에서 변경되었을 것입니다.

전달 된 변수의 별칭으로 참조 매개 변수를 생각해보십시오. 별칭이 할당되면 전달 된 변수도 할당됩니다.

Question

나는 자바가 패스 - 바이 - 레퍼런스 였다고 생각했지만, 그렇지 않다는 주장을하는 몇 개의 블로그 포스트 (예를 들어, 이 블로그 )를 보았다. 나는 그들이하는 구별을 이해하지 못한다고 생각한다.

설명은 무엇입니까?




A reference is always a value when represented, no matter what language you use.

Getting an outside of the box view, let's look at Assembly or some low level memory management. At the CPU level a reference to anything immediately becomes a value if it gets written to memory or to one of the CPU registers. (That is why pointer is a good definition. It is a value, which has a purpose at the same time).

Data in memory has a Location and at that location there is a value (byte,word, whatever). In Assembly we have a convenient solution to give a Name to certain Location (aka variable), but when compiling the code, the assembler simply replaces Name with the designated location just like your browser replaces domain names with IP addresses.

Down to the core it is technically impossible to pass a reference to anything in any language without representing it (when it immediately becomes a value).

Lets say we have a variable Foo, its Location is at the 47th byte in memory and its Value is 5. We have another variable Ref2Foo which is at 223rd byte in memory, and its value will be 47. This Ref2Foo might be a technical variable, not explicitly created by the program. If you just look at 5 and 47 without any other information, you will see just two Values . If you use them as references then to reach to 5 we have to travel:

(Name)[Location] -> [Value at the Location]
---------------------
(Ref2Foo)[223]  -> 47
(Foo)[47]       -> 5

This is how jump-tables work.

If we want to call a method/function/procedure with Foo's value, there are a few possible way to pass the variable to the method, depending on the language and its several method invocation modes:

  1. 5 gets copied to one of the CPU registers (ie. EAX).
  2. 5 gets PUSHd to the stack.
  3. 47 gets copied to one of the CPU registers
  4. 47 PUSHd to the stack.
  5. 223 gets copied to one of the CPU registers.
  6. 223 gets PUSHd to the stack.

In every cases above a value - a copy of an existing value - has been created, it is now upto the receiving method to handle it. When you write "Foo" inside the method, it is either read out from EAX, or automatically dereferenced , or double dereferenced, the process depends on how the language works and/or what the type of Foo dictates. This is hidden from the developer until she circumvents the dereferencing process. So a reference is a value when represented, because a reference is a value that has to be processed (at language level).

Now we have passed Foo to the method:

  • in case 1. and 2. if you change Foo ( Foo = 9 ) it only affects local scope as you have a copy of the Value. From inside the method we cannot even determine where in memory the original Foo was located.
  • in case 3. and 4. if you use default language constructs and change Foo ( Foo = 11 ), it could change Foo globally (depends on the language, ie. Java or like Pascal's procedure findMin(x, y, z: integer; var m : integer); ). However if the language allows you to circumvent the dereference process, you can change 47 , say to 49 . At that point Foo seems to have been changed if you read it, because you have changed the local pointer to it. And if you were to modify this Foo inside the method ( Foo = 12 ) you will probably FUBAR the execution of the program (aka. segfault) because you will write to a different memory than expected, you can even modify an area that is destined to hold executable program and writing to it will modify running code (Foo is now not at 47 ). BUT Foo's value of 47 did not change globally, only the one inside the method, because 47 was also a copy to the method.
  • in case 5. and 6. if you modify 223 inside the method it creates the same mayhem as in 3. or 4. (a pointer, pointing to a now bad value, that is again used as a pointer) but this is still a local problem, as 223 was copied . However if you are able to dereference Ref2Foo (that is 223 ), reach to and modify the pointed value 47 , say, to 49 , it will affect Foo globally , because in this case the methods got a copy of 223 but the referenced 47 exists only once, and changing that to 49 will lead every Ref2Foo double-dereferencing to a wrong value.

Nitpicking on insignificant details, even languages that do pass-by-reference will pass values to functions, but those functions know that they have to use it for dereferencing purposes. This pass-the-reference-as-value is just hidden from the programmer because it is practically useless and the terminology is only pass-by-reference .

Strict pass-by-value is also useless, it would mean that a 100 Mbyte array should have to be copied every time we call a method with the array as argument, therefore Java cannot be stricly pass-by-value. Every language would pass a reference to this huge array (as a value) and either employs copy-on-write mechanism if that array can be changed locally inside the method or allows the method (as Java does) to modify the array globally (from the caller's view) and a few languages allows to modify the Value of the reference itself.

So in short and in Java's own terminology, Java is pass-by-value where value can be: either a real value or a value that is a representation of a reference .




As far as I know, Java only knows call by value. This means for primitive datatypes you will work with an copy and for objects you will work with an copy of the reference to the objects. However I think there are some pitfalls; for example, this will not work:

public static void swap(StringBuffer s1, StringBuffer s2) {
    StringBuffer temp = s1;
    s1 = s2;
    s2 = temp;
}


public static void main(String[] args) {
    StringBuffer s1 = new StringBuffer("Hello");
    StringBuffer s2 = new StringBuffer("World");
    swap(s1, s2);
    System.out.println(s1);
    System.out.println(s2);
}

This will populate Hello World and not World Hello because in the swap function you use copys which have no impact on the references in the main. But if your objects are not immutable you can change it for example:

public static void appendWorld(StringBuffer s1) {
    s1.append(" World");
}

public static void main(String[] args) {
    StringBuffer s = new StringBuffer("Hello");
    appendWorld(s);
    System.out.println(s);
}

This will populate Hello World on the command line. If you change StringBuffer into String it will produce just Hello because String is immutable. 예 :

public static void appendWorld(String s){
    s = s+" World";
}

public static void main(String[] args) {
    String s = new String("Hello");
    appendWorld(s);
    System.out.println(s);
}

However you could make a wrapper for String like this which would make it able to use it with Strings:

class StringWrapper {
    public String value;

    public StringWrapper(String value) {
        this.value = value;
    }
}

public static void appendWorld(StringWrapper s){
    s.value = s.value +" World";
}

public static void main(String[] args) {
    StringWrapper s = new StringWrapper("Hello");
    appendWorld(s);
    System.out.println(s.value);
}

edit: i believe this is also the reason to use StringBuffer when it comes to "adding" two Strings because you can modifie the original object which u can't with immutable objects like String is.




아무도 Barbara Liskov를 아직 언급하지 않았다는 것을 나는 믿을 수 없다. 그녀가 1974 년에 CLU를 디자인했을 때, 그녀는이 동일한 전문 용어 문제에 부딪 쳤습니다. 그리고 그녀는이 "call by value"의 특정 사례에 대해 공유 (object-sharingobject에 의한 호출 이라고도 함)라는 용어를 발명했습니다. 참조 ".




Java copies the reference by value. So if you change it to something else (eg, using new ) the reference does not change outside the method. For native types, it is always pass by value.




이렇게하면 Java가 실제로 어떻게 동작하는지에 대한 통찰력을 얻을 수 있습니다. 자바에 대한 다음 논의에서 참조로 전달하거나 값으로 전달하면 웃을 것입니다 .-)

1 단계는 'p'로 시작하는 단어를 지우십시오. "_ _ _ _ _ _ _"특히 다른 프로그래밍 언어에서 온 경우 자바와 'p'는 같은 책, 포럼 또는 심지어 txt로 쓰여질 수 없습니다.

두 번째 단계는 Object를 메서드로 전달할 때 Object 자체가 아니라 Object 참조를 전달한다는 것을 기억하십시오.

  • Student : Master, Java가 참조로 전달된다는 의미입니까?
  • 마스터 : 메뚜기, 아니.

이제 개체의 참조 / 변수가하는 일을 생각해보십시오.

  1. 변수는 메모리에서 참조 된 객체 (힙)로가는 방법을 JVM에 알려주는 비트를 포함합니다.
  2. 메소드에 인수를 전달할 때 참조 변수는 전달하지 않지만 참조 변수는 해당 비트의 사본을 전달합니다 . 이 같은 것 : 3bad086a. 3bad086a는 전달 된 객체에 도달하는 방법을 나타냅니다.
  3. 따라서 참조의 가치라는 것을 3bad086a에 전달하는 것입니다.
  4. 참조 자체가 아니라 객체 자체가 아닌 참조 값을 전달합니다.
  5. 이 값은 실제로 COPIED되어 메소드에 주어집니다 .

다음 (컴파일 / 실행하지 마십시오 ...) :

1. Person person;
2. person = new Person("Tom");
3. changeName(person);
4.
5. //I didn't use Person person below as an argument to be nice
6. static void changeName(Person anotherReferenceToTheSamePersonObject) {
7.     anotherReferenceToTheSamePersonObject.setName("Jerry");
8. }

무슨 일이야?

  • 변수 person 은 # 1 행에 작성되며 처음에는 null입니다.
  • 새로운 Person 객체가 # 2 행에 생성되어 메모리에 저장되고, 변수 person 에 Person 객체에 대한 참조가 제공됩니다. 즉, 그 주소입니다. 3bad086a라고 가정 해 봅시다.
  • Object의 주소를 가지고있는 변수 person 은 라인 # 3의 함수에 전달됩니다.
  • 4 번 줄에서는 침묵의 소리를들을 수 있습니다.
  • 5 번 줄에 주석 달기
  • 메서드 로컬 변수 인 anotherReferenceToTheSamePersonObject 가 만들어지고 그 다음에는 # 6 행의 마법 이옵니다 .
    • 변수 / 참조 은 비트 단위로 복사되고 함수 내에서 anotherReferenceToTheSamePersonObject에 전달됩니다.
    • Person의 새 인스턴스는 작성되지 않습니다.
    • " person "과 " anotherReferenceToTheSamePersonObject "는 모두 3bad086a 와 동일한 값을 유지합니다.
    • 이 것을 시도하지 말고 person == anotherReferenceToTheSamePersonObject가 true가됩니다.
    • 두 변수 모두 참조에 대해 동일한 사본을 가지고 있으며 둘 다 동일한 개인 오브젝트, 힙의 SAME 오브젝트 및 사본이 아 U니다.

그림은 천 단어의 가치가있다 :

다른 ReferencesToTheSamePersonObject 화살표는 오브젝트쪽으로 향하고 가변 사람쪽으로 향하지 않습니다!

당신이 그것을 얻지 못한다면 나는 단지 나를 신뢰하고 자바가 가치에 의한 것임을 말하는 것이 낫다는 것을 기억하십시오. 음, 기준값으로 전달하십시오 . 오, 더 나은 것은 패스 바이 바이 변이 가치입니다! ;)

이제 나를 미워하되,이 점을 고려할 때 메소드 인자에 대해 말할 때 원시 데이터 유형과 객체를 전달하는 것 사이에는 차이가 없다는 점에 유의하십시오.

당신은 항상 참조 값의 비트의 복사본을 전달합니다!

  • 기본 데이터 형식 인 경우이 비트는 기본 데이터 형식 자체의 값을 포함합니다.
  • 오브젝트의 경우, 비트는 JVM에 오브젝트에 도달하는 방법을 알려주는 주소 값을 포함합니다.

Java는 메서드 내에서 참조 된 Object를 원하는만큼 수정할 수 있기 때문에 값으로 전달됩니다.하지만 아무리 노력해도 참조 된 변수를 수정할 수 없으므로 (p _ _ _이 아닌) _ _ _ _) 같은 개체 상관없이!

위의 changeName 함수는 전달 된 참조의 실제 내용 (비트 값)을 수정할 수 없습니다. 즉, changeName은 사람이 다른 사람을 참조하도록 만들 수 없습니다.

당연히 당신은 그것을 짧게자를 수 있고 자바가 가치에 의한 것이라고 말할 수 있습니다 !




대조를 보여주기 위해 다음 C++Java 스니 j을 비교하십시오.

C ++에서 : 주의 : 나쁜 코드 - 메모리 누수! 그러나 그것은 그 요점을 보여줍니다.

void cppMethod(int val, int &ref, Dog obj, Dog &objRef, Dog *objPtr, Dog *&objPtrRef)
{
    val = 7; // Modifies the copy
    ref = 7; // Modifies the original variable
    obj.SetName("obj"); // Modifies the copy of Dog passed
    objRef.SetName("objRef"); // Modifies the original Dog passed
    objPtr->SetName("objPtr"); // Modifies the original Dog pointed to 
                               // by the copy of the pointer passed.
    objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                   // leaving the original object alone.
    objPtrRef->SetName("objRefPtr"); // Modifies the original Dog pointed to 
                                    // by the original pointer passed. 
    objPtrRef = new Dog("newObjPtrRef"); // Modifies the original pointer passed
}

int main()
{
    int a = 0;
    int b = 0;
    Dog d0 = Dog("d0");
    Dog d1 = Dog("d1");
    Dog *d2 = new Dog("d2");
    Dog *d3 = new Dog("d3");
    cppMethod(a, b, d0, d1, d2, d3);
    // a is still set to 0
    // b is now set to 7
    // d0 still have name "d0"
    // d1 now has name "objRef"
    // d2 now has name "objPtr"
    // d3 now has name "newObjPtrRef"
}

자바에서는,

public static void javaMethod(int val, Dog objPtr)
{
   val = 7; // Modifies the copy
   objPtr.SetName("objPtr") // Modifies the original Dog pointed to 
                            // by the copy of the pointer passed.
   objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                  // leaving the original object alone.
}

public static void main()
{
    int a = 0;
    Dog d0 = new Dog("d0");
    javaMethod(a, d0);
    // a is still set to 0
    // d0 now has name "objPtr"
}

Java는 내장 유형에 대한 값과 오브젝트 유형에 대한 포인터 값의 두 가지 유형 만 전달합니다.




In Java only references are passed and are passed by value:

Java arguments are all passed by value (the reference is copied when used by the method) :

In the case of primitive types, Java behaviour is simple: The value is copied in another instance of the primitive type.

In case of Objects, this is the same: Object variables are pointers (buckets) holding only Object's address that was created using the "new" keyword, and are copied like primitive types.

The behaviour can appear different from primitive types: Because the copied object-variable contains the same address (to the same Object) Object's content/members might still be modified within a method and later access outside, giving the illusion that the (containing) Object itself was passed by reference.

"String" Objects appear to be a perfect counter-example to the urban legend saying that "Objects are passed by reference":

In effect, within a method you will never be able, to update the value of a String passed as argument:

A String Object, holds characters by an array declared final that can't be modified. Only the address of the Object might be replaced by another using "new". Using "new" to update the variable, will not let the Object be accessed from outside, since the variable was initially passed by value and copied.




Java는 참조로 값을 전달합니다.

따라서 전달 된 참조를 변경할 수 없습니다.




Java is always pass by value, not pass by reference

First of all, we need to understand what pass by value and pass by reference are.

Pass by value means that you are making a copy in memory of the actual parameter's value that is passed in. This is a copy of the contents of the actual parameter .

Pass by reference (also called pass by address) means that a copy of the address of the actual parameter is stored .

Sometimes Java can give the illusion of pass by reference. Let's see how it works by using the example below:

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeValue(t);
        System.out.println(t.name);
    }

    public void changeValue(Test f) {
        f.name = "changevalue";
    }
}

class Test {
    String name;
}

The output of this program is:

changevalue

Let's understand step by step:

Test t = new Test();

As we all know it will create an object in the heap and return the reference value back to t. For example, suppose the value of t is 0x100234 (we don't know the actual JVM internal value, this is just an example) .

new PassByValue().changeValue(t);

When passing reference t to the function it will not directly pass the actual reference value of object test, but it will create a copy of t and then pass it to the function. Since it is passing by value , it passes a copy of the variable rather than the actual reference of it. Since we said the value of t was 0x100234 , both t and f will have the same value and hence they will point to the same object.

If you change anything in the function using reference f it will modify the existing contents of the object. That is why we got the output changevalue , which is updated in the function.

To understand this more clearly, consider the following example:

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeRefence(t);
        System.out.println(t.name);
    }

    public void changeRefence(Test f) {
        f = null;
    }
}

class Test {
    String name;
}

Will this throw a NullPointerException ? No, because it only passes a copy of the reference. In the case of passing by reference, it could have thrown a NullPointerException , as seen below:

Hopefully this will help.




The crux of the matter is that the word reference in the expression "pass by reference" means something completely different from the usual meaning of the word reference in Java.

Usually in Java reference means aa reference to an object . But the technical terms pass by reference/value from programming language theory is talking about a reference to the memory cell holding the variable , which is something completely different.




The distinction, or perhaps just the way I remember as I used to be under the same impression as the original poster is this: Java is always pass by value. All objects( in Java, anything except for primitives) in Java are references. These references are passed by value.




I always think of it as "pass by copy". It is a copy of the value be it primitive or reference. If it is a primitive it is a copy of the bits that are the value and if it is an Object it is a copy of the reference.

public class PassByCopy{
    public static void changeName(Dog d){
        d.name = "Fido";
    }
    public static void main(String[] args){
        Dog d = new Dog("Maxx");
        System.out.println("name= "+ d.name);
        changeName(d);
        System.out.println("name= "+ d.name);
    }
}
class Dog{
    public String name;
    public Dog(String s){
        this.name = s;
    }
}

output of java PassByCopy:

name= Maxx
name= Fido

Primitive wrapper classes and Strings are immutable so any example using those types will not work the same as other types/objects.




To make a long story short, Java objects have some very peculiar properties.

In general, Java has primitive types ( int , bool , char , double , etc) that are passed directly by value. Then Java has objects (everything that derives from java.lang.Object ). Objects are actually always handled through a reference (a reference being a pointer that you can't touch). That means that in effect, objects are passed by reference, as the references are normally not interesting. It does however mean that you cannot change which object is pointed to as the reference itself is passed by value.

Does this sound strange and confusing? Let's consider how C implements pass by reference and pass by value. In C, the default convention is pass by value. void foo(int x) passes an int by value. void foo(int *x) is a function that does not want an int a , but a pointer to an int: foo(&a) . One would use this with the & operator to pass a variable address.

Take this to C++, and we have references. References are basically (in this context) syntactic sugar that hide the pointer part of the equation: void foo(int &x) is called by foo(a) , where the compiler itself knows that it is a reference and the address of the non-reference a should be passed. In Java, all variables referring to objects are actually of reference type, in effect forcing call by reference for most intends and purposes without the fine grained control (and complexity) afforded by, for example, C++.




You can never pass by reference in Java, and one of the ways that is obvious is when you want to return more than one value from a method call. Consider the following bit of code in C++:

void getValues(int& arg1, int& arg2) {
    arg1 = 1;
    arg2 = 2;
}
void caller() {
    int x;
    int y;
    getValues(x, y);
    cout << "Result: " << x << " " << y << endl;
}

Sometimes you want to use the same pattern in Java, but you can't; at least not directly. Instead you could do something like this:

void getValues(int[] arg1, int[] arg2) {
    arg1[0] = 1;
    arg2[0] = 2;
}
void caller() {
    int[] x = new int[1];
    int[] y = new int[1];
    getValues(x, y);
    System.out.println("Result: " + x[0] + " " + y[0]);
}

As was explained in previous answers, in Java you're passing a pointer to the array as a value into getValues . That is enough, because the method then modifies the array element, and by convention you're expecting element 0 to contain the return value. Obviously you can do this in other ways, such as structuring your code so this isn't necessary, or constructing a class that can contain the return value or allow it to be set. But the simple pattern available to you in C++ above is not available in Java.




Links