Java是“通過引用傳遞”還是“按值傳遞”?


Answers

我只是注意到你引用了我的文章

Java Spec說,Java中的所有東西都是按值傳遞的。 Java中沒有“傳遞參考”這樣的東西。

理解這一點的關鍵是類似的東西

Dog myDog;

不是狗; 它實際上是一個指向狗的指針

這意味著什麼,當你有

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

你基本上把創建的Dog對象的地址傳遞給foo方法。

(我說的本質上是因為Java指針不是直接地址,但最簡單的方法就是這樣想)

假設Dog對象駐留在內存地址42上。這意味著我們將42傳遞給該方法。

如果方法被定義為

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

讓我們看看發生了什麼。

  • 參數someDog被設置為值42
  • 在“AAA”行
    • someDog Dog跟隨它指向的Dog (地址42處的Dog對象)
    • Dog (地址42處的Dog )被要求將他的名字改為Max
  • 在“BBB”行
    • 一個新的Dog被創建。 假設他在地址74
    • 我們將參數someDog分配給74
  • 在“CCC”行
    • 有些Dog跟隨它指向的Dog (地址為74的Dog對象)
    • Dog (地址為74的那個)被要求將他的名字改為Rowlf
  • 那麼,我們回來

現在讓我們考慮一下在該方法之外發生的事情:

myDog是否改變了?

有關鍵。

記住myDog是一個指針 ,而不是真正的Dog ,答案是NO。 myDog仍然有值42; 它仍然指向原來的Dog (但請注意,因為“AAA”行,它的名字現在是“最大” - 仍然是狗; myDog的值沒有改變。)

遵循地址並改變結尾是完全有效的; 但不會改變變量。

Java的工作方式與C完全一樣。您可以指定一個指針,將指針傳遞給方法,跟隨方法中的指針並更改指向的數據。 但是,您無法更改指針指向的位置。

在C ++,Ada,Pascal和其他支持通過引用的語言中,您實際上可以更改傳遞的變量。

如果Java具有引用傳遞語義,那麼我們上面定義的foo方法將在myDog指向BBB上的myDog時指向的位置發生更改。

將引用參數看作是傳入變量的別名。當分配了別名時,傳入的變量也是如此。

Question

我一直認為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引用或傳遞值的討論中,您只會微笑:-)

第一步請從頭腦中清除以'p'開頭的詞,尤其是如果你來自其他編程語言。 Java和'p'不能寫在同一本書,論壇甚至txt中。

第二步記住,當你將一個對像傳遞給一個方法時,你傳遞的是對象引用而不是對象本身。

  • 學生 :師父,這是否意味著Java是通過引用?
  • 主人 :蚱蜢,號

現在想一下Object的引用/變量是做什麼的:

  1. 一個變量保存指示JVM如何到達內存中引用的對象(Heap)的位。
  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. }

怎麼了?

  • 變量人員在#1行中創建,並在開始時為空。
  • 一個新的Person對像在第2行中創建,存儲在內存中,變量Person被賦予對Person對象的引用。 就是它的地址。 假設3bad086a。
  • 持有對像地址的變量人員被傳遞給第3行中的函數。
  • 在#4線你可以聽到沉默的聲音
  • 檢查第5行的評論
  • 一個方法局部變量 - anotherReferenceToTheSamePersonObject - 被創建,然後在#6行出現魔法:
    • 變量/引用被逐位複制並傳遞給函數內的另一個參考對象。
    • 沒有創建Person的新實例。
    • person ”和“ anotherReferenceToTheSamePersonObject ”都保持3bad086a的相同值。
    • 不要嘗試這個,但人== anotherReferenceToTheSamePersonObject將是真實的。
    • 這兩個變量都有引用的IDENTICAL COPIES,它們都引用同一個Person對象,堆上的SAME對象而不是COPY。

一張圖片勝過千言萬語:

請注意,anotherReferenceToTheSamePersonObject箭頭指向對象,而不是指向變量人!

如果你沒有得到它,那麼請相信我,記住最好說Java是通過價值傳遞的 。 那麼, 通過參考值 。 哦,更好的是傳遞可變值的副本! ;)

現在可以自由地討厭我了,但請注意,在討論方法參數時,傳遞原始數據類型和對象之間沒有區別

您始終傳遞參考值的位的副本!

  • 如果它是基本數據類型,這些位將包含基本數據類型本身的值。
  • 如果它是一個Object,這些位將包含告訴JVM如何到達Object的地址的值。

Java是通過值傳遞的,因為在一個方法中,您可以盡可能多地修改引用的對象,但無論您嘗試多麼努力,都將永遠無法修改將繼續引用的傳遞變量(而不是p _ _ _ _ _ _ _)相同的對象無論如何!

上面的changeName函數將永遠不能修改傳入引用的實際內容(位值)。 換句話說,changeName不能讓Person人引用另一個Object。

當然你可以縮短它,只是說Java是通過值的!




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.




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.




我不敢相信沒有人提到芭芭拉·利斯科夫。 當她在1974年設計CLU時,她遇到了同樣的術語問題,她發明了通過共享 (也稱為通過對象共享按對象呼叫呼叫的術語,一個參考“。




Java按值傳遞引用。

所以你不能改變傳入的引用。




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++.




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.




為了顯示對比,請比較以下C++Java片段:

在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"
}

在Java中,

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只有兩種傳遞方式:按內置類型的值和按對像類型指針的值。




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.




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.




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.




Links