澄清:Java中只有按值传递,没有按引用传递! 博客分类: 编程技术 JavaC#CC++面试
程序员文章站
2024-02-16 21:00:46
...
前言:在JAVA面试题解惑系列(五)——传了值还是传了引用?中作者提到了“JAVA中的传递都是值传递吗?有没有引用传递呢? ”这个问题,最终得到:
事实上有着这种想法的人为数不少。但这个结论不完全正确。正确的说法应该是:在Java中,只有按值传递,没有按引用传递!
简单说,这里其实就是一个关于什么是“按引用传递”的问题。
如果你写了这样一个方法:
并且像下面这样调用该方法:
确实能调换var1与var2的值,才可能是“按引用传递”
有关这个问题的进一步解释,我这儿不再赘述,只给出两篇不错的文章:
☆ Does Java pass by reference or pass by value?
☆ Java is Pass-by-Value, Dammit!
下面是第二篇的全文,有空再翻译:
Introduction
I finally decided to write up a little something about Java's parameter passing. I'm really tired of hearing folks (incorrectly) state "primitives are passed by value, objects are passed by reference".
I'm a compiler guy at heart. The terms "pass-by-value" semantics and "pass-by-reference" semantics have very precise definitions, and they're often horribly abused when folks talk about Java. I want to correct that... The following is how I'd describe these
Pass-by-value
The actual parameter (or argument expression) is fully evaluated and the resulting value is copied into a location being used to hold the formal parameter's value during method/function execution. That location is typically a chunk of memory on the runtime stack for the application (which is how Java handles it), but other languages could choose parameter storage differently.
Pass-by-reference
The formal parameter merely acts as an alias for the actual parameter. Anytime the method/function uses the formal parameter (for reading or writing), it is actually using the actual parameter.
Java is strictly pass-by-value, exactly as in C. Read the Java Language Specification (JLS). It's spelled out, and it's correct. (See http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#37472)
In short: Java has pointers and is strictly pass-by-value. There's no funky rules. It's simple, clean, and clear. (Well, as clear as the evil C++-like syntax will allow ;)
Note: See the note at the end of this article for the semantics of remote method invocation (RMI). What is typically called "pass by reference" for remote objects is actually incredibly bad semantics.
The Litmus Test
There's a simple "litmus test" for whether a language supports pass-by-reference semantics:
Can you write a traditional swap(a,b) method/function in the language?
A traditional swap method or function takes two arguments and swaps them such that variables passed into the function are changed outside the function. Its basic structure looks like
If you can write such a method/function in your language such that calling
actually switches the values of the variables var1 and var2, the language supports pass-by-reference semantics.
For example, in Pascal, you can write
{ Pascal }
...
{ in some other procedure/function/program }
or in C++ you could write
...
(Please let me know if my Pascal or C++ has lapsed and I've messed up the syntax...)
But you cannot do this in Java!
Now the details...
The problem we're facing here is statements like
In Java, Objects are passed by reference, and primitives are passed by value.
This is half incorrect. Everyone can easily agree that primitives are passed by value; there's no such thing in Java as a pointer/reference to a primitive.
However, Objects are not passed by reference. A correct statement would be Object references are passed by value.
This may seem like splitting hairs, bit it is far from it. There is a world of difference in meaning. The following examples should help make the distinction.
In Java, take the case of
the variable passed in (aDog) is not modified! After calling foo, aDog still points to the "Max" Dog!
Many people mistakenly think/state that something like
shows that Java does in fact pass objects by reference.
The mistake they make is in the definition of
itself. When you write
you are defining a pointer to a Dog object, not a Dog object itself.
Calling
passes the value of d to foo; it does not pass the object that d points to!
The value of the pointer being passed is similar to a memory address. Under the covers it's a tad different, but you can think of it in exactly the same way. The value uniquely identifies some object on the heap.
The use of the word "reference" in Java was an incredibly poor choice (in my not-so-humble opinion...) Java has pointers, plain and simple. The designers of Java wanted to try to make a distinction between C/C++ pointers and Java pointers, so they picked another term. Under the covers, pointers are implemented very differently in Java and C/C++, and Java protects the pointer values, disallowing operations such as pointer arithmetic and invalid runtime casting.
However, it makes no difference how pointers are implemented under the covers. You program with them exactly the same way in Java as you would in C or C++. The syntax is just slightly different.
In Java,
is exactly like C or C++'s
And using
is exactly like C++'s
To sum up: Java has pointers, and the value of the pointer is passed in. There's no way to actually pass an object itself as a parameter. You can only pass a pointer to an object.
Keep in mind, when you call
you're not passing an object; you're passing a pointer to the object.
For a slightly different (but still correct) take on this issue, please see http://www-106.ibm.com/developerworks/library/j-praxis/pr1.html. It's from Peter Haggar's excellent book, Practical Java.)
A Note on Remote Method Invocation (RMI)
When passing parameters to remote methods, things get a bit more complex. First, we're (usually) dealing with passing data between two independent virtual machines, which might be on separate physical machines as well. Passing the value of a pointer wouldn't do any good, as the target virtual machine doesn't have access to the caller's heap.
You'll often hear "pass by value" and "pass by reference" used with respect to RMI. These terms have more of a "logical" meaning, and really aren't correct for the intended use.
Here's what is usually meant by these phrases with regard to RMI. Note that this is not proper usage of "pass by value" and "pass by reference" semantics:
RMI Pass-by-value
The actual parameter is serialized and passed using a network protocol to the target remote object. Serialization essentially "squeezes" the data out of an object/primitive. On the receiving end, that data is used to build a "clone" of the original object or primitive. Note that this process can be rather expensive if the actual parameters point to large objects (or large graphs of objects).
This isn't quite the right use of "pass-by-value"; I think it should really be called something like "pass-by-memento". (See "Design Patterns" by Gamma et al for a description of the Memento pattern).
RMI Pass-by-reference
The actual parameter, which is itself a remote object, is represented by a proxy. The proxy keeps track of where the actual parameter lives, and anytime the target method uses the formal parameter, another remote method invocation occurs to "call back" to the actual parameter. This can be useful if the actual parameter points to a large object (or graph of objects) and there are few call backs.
This isn't quite the right use of "pass-by-reference" (again, you cannot change the actual parameter itself). I think it should be called something like "pass-by-proxy". (Again, see "Design Patterns" for descriptions of the Proxy pattern).
引用
最后我们得出如下的结论:
1. 基本类型和基本类型变量被当作参数传递给方法时,是值传递。在方法实体中,无法给原变量重新赋值,也无法改变它的值。
2. 对象和引用型变量被当作参数传递给方法时,是引用传递。在方法实体中,无法给原变量重新赋值,但是可以改变它所指向对象的属性。
1. 基本类型和基本类型变量被当作参数传递给方法时,是值传递。在方法实体中,无法给原变量重新赋值,也无法改变它的值。
2. 对象和引用型变量被当作参数传递给方法时,是引用传递。在方法实体中,无法给原变量重新赋值,但是可以改变它所指向对象的属性。
事实上有着这种想法的人为数不少。但这个结论不完全正确。正确的说法应该是:在Java中,只有按值传递,没有按引用传递!
简单说,这里其实就是一个关于什么是“按引用传递”的问题。
如果你写了这样一个方法:
swap(Type arg1, Type arg2) { Type temp = arg1; arg1 = arg2; arg2 = temp; }
并且像下面这样调用该方法:
Type var1 = ...; Type var2 = ...; swap(var1,var2);
确实能调换var1与var2的值,才可能是“按引用传递”
有关这个问题的进一步解释,我这儿不再赘述,只给出两篇不错的文章:
☆ Does Java pass by reference or pass by value?
☆ Java is Pass-by-Value, Dammit!
下面是第二篇的全文,有空再翻译:
Introduction
I finally decided to write up a little something about Java's parameter passing. I'm really tired of hearing folks (incorrectly) state "primitives are passed by value, objects are passed by reference".
I'm a compiler guy at heart. The terms "pass-by-value" semantics and "pass-by-reference" semantics have very precise definitions, and they're often horribly abused when folks talk about Java. I want to correct that... The following is how I'd describe these
Pass-by-value
The actual parameter (or argument expression) is fully evaluated and the resulting value is copied into a location being used to hold the formal parameter's value during method/function execution. That location is typically a chunk of memory on the runtime stack for the application (which is how Java handles it), but other languages could choose parameter storage differently.
Pass-by-reference
The formal parameter merely acts as an alias for the actual parameter. Anytime the method/function uses the formal parameter (for reading or writing), it is actually using the actual parameter.
Java is strictly pass-by-value, exactly as in C. Read the Java Language Specification (JLS). It's spelled out, and it's correct. (See http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#37472)
In short: Java has pointers and is strictly pass-by-value. There's no funky rules. It's simple, clean, and clear. (Well, as clear as the evil C++-like syntax will allow ;)
Note: See the note at the end of this article for the semantics of remote method invocation (RMI). What is typically called "pass by reference" for remote objects is actually incredibly bad semantics.
The Litmus Test
There's a simple "litmus test" for whether a language supports pass-by-reference semantics:
Can you write a traditional swap(a,b) method/function in the language?
A traditional swap method or function takes two arguments and swaps them such that variables passed into the function are changed outside the function. Its basic structure looks like
// NON-JAVA! swap(Type arg1, Type arg2) { Type temp = arg1; arg1 = arg2; arg2 = temp; }
If you can write such a method/function in your language such that calling
// NON-JAVA Type var1 = ...; Type var2 = ...; swap(var1,var2);
actually switches the values of the variables var1 and var2, the language supports pass-by-reference semantics.
For example, in Pascal, you can write
{ Pascal }
procedure swap(var arg1, arg2: SomeType); var temp : SomeType; begin temp := arg1; arg1 := arg2; arg2 := temp; end;
...
{ in some other procedure/function/program }
var var1, var2 : SomeType; begin var1 := ...; var2 := ...; swap(var1, var2); end;
or in C++ you could write
// C++ void swap(SomeType& arg1, Sometype& arg2) { SomeType temp = arg1; arg1 = arg2; arg2 = temp; }
...
SomeType var1 = ...; SomeType var2 = ...; swap(var1, var2); // swaps their values!
(Please let me know if my Pascal or C++ has lapsed and I've messed up the syntax...)
But you cannot do this in Java!
Now the details...
The problem we're facing here is statements like
In Java, Objects are passed by reference, and primitives are passed by value.
This is half incorrect. Everyone can easily agree that primitives are passed by value; there's no such thing in Java as a pointer/reference to a primitive.
However, Objects are not passed by reference. A correct statement would be Object references are passed by value.
This may seem like splitting hairs, bit it is far from it. There is a world of difference in meaning. The following examples should help make the distinction.
In Java, take the case of
public void foo(Dog d) { d = new Dog("Fifi"); } Dog aDog = new Dog("Max"); foo(aDog);
the variable passed in (aDog) is not modified! After calling foo, aDog still points to the "Max" Dog!
Many people mistakenly think/state that something like
public void foo(Dog d) { d.setName("Fifi"); }
shows that Java does in fact pass objects by reference.
The mistake they make is in the definition of
Dog d;
itself. When you write
Dog d;
you are defining a pointer to a Dog object, not a Dog object itself.
Calling
foo(d);
passes the value of d to foo; it does not pass the object that d points to!
The value of the pointer being passed is similar to a memory address. Under the covers it's a tad different, but you can think of it in exactly the same way. The value uniquely identifies some object on the heap.
The use of the word "reference" in Java was an incredibly poor choice (in my not-so-humble opinion...) Java has pointers, plain and simple. The designers of Java wanted to try to make a distinction between C/C++ pointers and Java pointers, so they picked another term. Under the covers, pointers are implemented very differently in Java and C/C++, and Java protects the pointer values, disallowing operations such as pointer arithmetic and invalid runtime casting.
However, it makes no difference how pointers are implemented under the covers. You program with them exactly the same way in Java as you would in C or C++. The syntax is just slightly different.
In Java,
Dog d; // Java
is exactly like C or C++'s
Dog *d; // C++
And using
d.setName("Fifi"); // Java
is exactly like C++'s
d->setName("Fifi"); // C++
To sum up: Java has pointers, and the value of the pointer is passed in. There's no way to actually pass an object itself as a parameter. You can only pass a pointer to an object.
Keep in mind, when you call
foo(d);
you're not passing an object; you're passing a pointer to the object.
For a slightly different (but still correct) take on this issue, please see http://www-106.ibm.com/developerworks/library/j-praxis/pr1.html. It's from Peter Haggar's excellent book, Practical Java.)
A Note on Remote Method Invocation (RMI)
When passing parameters to remote methods, things get a bit more complex. First, we're (usually) dealing with passing data between two independent virtual machines, which might be on separate physical machines as well. Passing the value of a pointer wouldn't do any good, as the target virtual machine doesn't have access to the caller's heap.
You'll often hear "pass by value" and "pass by reference" used with respect to RMI. These terms have more of a "logical" meaning, and really aren't correct for the intended use.
Here's what is usually meant by these phrases with regard to RMI. Note that this is not proper usage of "pass by value" and "pass by reference" semantics:
RMI Pass-by-value
The actual parameter is serialized and passed using a network protocol to the target remote object. Serialization essentially "squeezes" the data out of an object/primitive. On the receiving end, that data is used to build a "clone" of the original object or primitive. Note that this process can be rather expensive if the actual parameters point to large objects (or large graphs of objects).
This isn't quite the right use of "pass-by-value"; I think it should really be called something like "pass-by-memento". (See "Design Patterns" by Gamma et al for a description of the Memento pattern).
RMI Pass-by-reference
The actual parameter, which is itself a remote object, is represented by a proxy. The proxy keeps track of where the actual parameter lives, and anytime the target method uses the formal parameter, another remote method invocation occurs to "call back" to the actual parameter. This can be useful if the actual parameter points to a large object (or graph of objects) and there are few call backs.
This isn't quite the right use of "pass-by-reference" (again, you cannot change the actual parameter itself). I think it should be called something like "pass-by-proxy". (Again, see "Design Patterns" for descriptions of the Proxy pattern).