實在看不下去網(wǎng)上的一些面試題,很多都是錯的答案。例如像今天這個問題:java方法用的是值傳遞還是引用傳遞。你在blogjava上還能搜到不同的答案呢。最近有空就翻譯了一篇國外的文章,很多東西不能只看答案,而不知其所以然。第一次翻譯文章,博友多指教。
重申:對于原始類型(primitive
type也譯為值類型),是通過拷貝一個相同的值傳給java方法的參數(shù)的;而對于引用類型(reference
type),就是對象,是通過拷貝一個相同的應(yīng)用或地址傳給java方法的參數(shù)的。業(yè)界都統(tǒng)稱這是pass-by-value(值傳遞),這里是翻譯一篇
國外的文章來說明為什么java中的值傳遞比較特別。
java中值傳遞比較特別,也比較有爭議,所以重要的是要理解它的原理。推薦Java程序員都去讀《Effective java》。
原文地址:http://java.sun.com/developer/JDCTechTips/2001/tt1009.html
另一篇寫的比較好的文章(英文):http://www.brpreiss.com/books/opus5/html/page590.html
blogjava上前輩的文章:http://www.tkk7.com/zygcs/archive/2008/10/05/232438.html
實際參數(shù)是如何傳遞到java方法的
(HOW ARGUMENTS ARE PASSED TO JAVA
METHODS)
Suppose you're doing some Java programming, and
you have a simple program like this:
假如你正在編寫java程序,然后你寫了以下一個程序:
1 public class CallDemo1 {
2 static void f(int arg1) {
3 arg1 = 10;
4 }
5
6 public static void main(String args[]) {
7 int arg1;
8
9 arg1 = 5;
10
11 f(arg1);
12
13 System.out.println("arg1 = " + arg1);
14 }
15 }
16
In the main method, the variable arg1 is given the value 5, and then
passed as an argument to the method f.
This method declares a parameter of the same name, arg1, used to access the argument.
在main方法中,變量arg1賦值5,然后作為實際參數(shù)傳遞到方法f。這個方法聲明一個相同名字的形式參數(shù)arg1,被用于訪問實際參數(shù)。
What happens when you run this simple program?
The method f modifies the
value of arg1, so what
value gets printed, 5 or 10? It turns out that 5 is the right answer. This
implies that setting arg1
to 10 in method f has no effect outside of the method.
當(dāng)你執(zhí)行這個程序會發(fā)生什么事情呢?方法f修改了arg1的值,所以會打印什么呢,5還是10?這里5是正確的答案。這就意味著在方法f中,將arg1設(shè)為10并不影響到方法的外部。
Why does it work this way? The answer has to do
with the distinction between the pass-by-value and pass-by-reference approaches
to passing arguments to a method. The Java language uses pass-by-value
exclusively. Before explaining what this means, let's look at an example of
pass-by-reference, using C++ code:
為什么呢?答案在于區(qū)分使用“值傳遞”方式和“引用傳遞”方式給方法傳遞參數(shù)。Java語言使用與眾不同的“值傳遞”方式。在解釋它的意思之前,讓我們看看以下一個值傳遞的例子,這是用C++寫的。
1 #include <iostream>
2
3 using namespace std;
4
5 void f(int arg1, int& arg2)
6 {
7 arg1 = 10;
8 arg2 = 10;
9 }
10
11 int main()
12 {
13 int arg1, arg2;
14
15 arg1 = 5;
16 arg2 = 5;
17
18 f(arg1, arg2);
19
20 cout << "arg1 = " << arg1 << " arg2 = "<< arg2 << endl;
22 }
23
Function f has two parameters. The first parameter is a pass-by-value
parameter, the second a pass-by-reference one. When you run this program, the
first assignment in function f
has no effect in the caller (the main function). But the second assignment
does, in fact, change the value of arg2
in main. The & in int& says that arg2 is a pass-by-reference parameter. This
particular example has no equivalent in Java programming.
函數(shù)f有兩個形參。第一個是值傳遞,第二個是引用傳遞。當(dāng)你運行這個程序的時候,第一個參數(shù)賦值不影響調(diào)用者(main函數(shù))。但是第二個參數(shù)賦值卻會,事實上,修改了main中arg2的值。在int&中的&表示arg2是一個引用傳遞的形參。這個特別的例子與java有所不同。
So what does pass-by-value actually mean? To
answer this, it's instructive to look at some JVM1 bytecodes that
result from the following two commands:
那值傳遞到底是什么意思?要回答這個問題,有需要看一下運行以下兩行命令后的JVM的字節(jié)碼:
javac
CallDemo1.java
javap -c
-classpath . CallDemo1
Here is an excerpt from the output:
這里是輸出的片段:
Method void f(int)
0 bipush 10
2 istore_0
3 return
Method void main(java.lang.String[])
0 iconst_5
1 istore_1
2 iload_1
3 invokestatic #2 <Method void f(int)>
In the main method, the instruction iconst_5 pushes the value 5 onto the operand stack of the Java
virtual machine. This value is then stored in the second local variable (arg1, with args as the first local
variable). iload_1 pushes the
value of the second local variable onto the operand stack, where it will serve
as an argument to the called method.
在main方法中,iconst_5指令將值5壓入JVM的操作數(shù)堆棧(operand
stack)。然后這個值隨后存儲第二個本地變量(arg1的值存在第一個本地變量處)。iload_1指令將第二個本地變量的值再壓入操作數(shù)堆棧,作為被調(diào)用方法的形參。
Then the method f is invoked using the invokestatic
instruction. The argument value is popped from the stack, and is used to create
a stack frame for the called method. This stack frame represents the local
variables in f, with the
method parameter (arg1)
as the first of the local variables.
然后方法f通過invokestatic指令調(diào)用。形參的值出棧,被用于為被調(diào)用的方法創(chuàng)建一個棧框架(stack frame)。這個棧框架表示所有f中的所有本地變量,而arg1實參將作為棧框架內(nèi)的第一個本地變量。
What this means is that the parameters in a
method are copies of the argument values passed to the method. If you modify a
parameter, it has no effect on the caller. You are simply changing the copy's
value in the stack frame that is used to hold local variables. There is no way
to "get back" at the arguments in the calling method. So assigning to
arg1 in f does not change arg1
in main. Also, the arg1 variables in f and in main are unrelated to each other, except that arg1 in
f starts with a copy of the value of arg1 in main. The variables occupy
different locations in memory, and the fact that they have the same name is
irrelevant.
這里的的意思是形式參數(shù)作為實際參數(shù)的拷貝,被傳入方法當(dāng)中。如果你(在方法體內(nèi))修改了一個實際參數(shù),它并不影響調(diào)用者,你只是修改了棧框架內(nèi)的拷貝而已。不可能在調(diào)用方法中“取回”實參的值。因此在f中給arg1賦值不會改變main中的arg1,而且這兩個arg1毫無關(guān)聯(lián),除了main中的arg1是f中的arg1的一個拷貝之外。這兩個變量存在內(nèi)存的不同區(qū)域,事實上雖然它們有同樣的名字,但是毫不相干。
By contrast, a pass-by-reference parameter is
implemented by passing the memory address of the caller's argument to the
called function. The argument address is copied into the parameter. The
parameter contains an address that references the argument's memory location so
that changes to the parameter actually change the argument value in the caller.
In low-level terms, if you have the memory address of a variable, you can
change the variable's value at will.
對比值傳遞,引用傳遞的參數(shù)是指傳遞調(diào)用者實參的內(nèi)存地址給被調(diào)用的函數(shù)。實參地址拷貝給形參。形參指向了實參的內(nèi)存區(qū)域,所以對形參的修改同樣會修改到實參。從底層的角度來說,你可以任意修改變量的值,只要你有該變量的內(nèi)存地址。
The discussion of argument passing is complicated
by the fact that the term "reference" in pass-by-reference means
something slightly different than the typical use of the term in Java
programming. In Java, the term reference is used in the context of object
references. When you pass an object reference to a method, you're not using
pass-by-reference, but pass-by-value. In particular, a copy is made of the
object reference argument value, and changes to the copy (through the
parameter) have no effect in the caller. Let's look at a couple of examples to
clarify this idea:
關(guān)于參數(shù)傳遞的討論會復(fù)雜化,這是因為以上引用傳遞中的術(shù)語“引用”與Java編程中使用的(術(shù)語)存在微妙的區(qū)別。在java中,術(shù)語“引用”用于對象所有引用的上下文。當(dāng)你給方法傳遞一個對象引用,你并不是使用引用傳遞,更像是值傳遞。尤其是,這樣會拷貝一份實際參數(shù)的對象引用,(通過形參)對該拷貝的修改不會影響到調(diào)用者。讓我們用以下兩個例子來證明這個觀點。
1 class A {
2
3 public int x;
4
5 A(int x) {
6 this.x = x;
7 }
8
9 public String toString() {
10 return Integer.toString(x);
11 }
12
13 }
14
15 public class CallDemo2 {
16
17 static void f(A arg1) {
18 arg1 = null;
19 }
20
21 public static void main(String args[]) {
22 A arg1 = new A(5);
23 f(arg1);
24 System.out.println("arg1 = " + arg1);
25 }
26
27 }
In this example, a reference to an A object is passed to f. Setting arg1 to null
in f has no
effect on the caller, just as in the previous example. The value 5 gets printed. The caller passes a
copy of the object reference value (arg1),
not the memory address of arg1.
So the called method cannot get back at arg1 and change it.
在這個例子中,A的引用傳遞給f。把arg1設(shè)置為null不影響調(diào)用者,就像前一個例子一樣。程序會打印5。調(diào)用者傳遞一份對象引用(arg1)的拷貝,而不是arg1的內(nèi)存地址。所以調(diào)用方法無法取回到(調(diào)用者的)arg1變量并修改它。
Here's another example:
1 class A {
2
3 public int x;
4
5 public A(int x) {
6 this.x = x;
7 }
8
9 public String toString() {
10 return Integer.toString(x);
11 }
12
13 }
14
15
16
17 public class CallDemo3 {
18
19 static void f(A arg1) {
20 arg1.x = 10;
21 }
22
23 public static void main(String args[]) {
24 A arg1 = new A(5);
25 f(arg1);
26 System.out.println("arg1 = " + arg1);
27 }
28
29 }
What gets printed here is 10. How can that be? You've already
seen that there's no way to change the caller's version of arg1 in the called method. But this code
shows that the object referenced by arg1
can be changed. Here, the calling method and the called method have an object
in common, and both methods can change the object. In this example, the object
reference (arg1) is passed
by value. Then a copy of it is made into the stack frame for f. But both the original and the copy
are object references, and they point to a common object in memory that can be
modified.
這個例子會打印10。為什么呢?通過之前那個例子,你已經(jīng)知道無法改變在調(diào)用方法中修改(實參)arg1。但是這里的代碼顯示arg1能夠被修改。這里調(diào)用方法和被調(diào)用方法有個共同的對象,兩個方法都能修改這個對象。在這個例子中,對象的引用(arg1)是值傳遞。他的一個拷貝被加入到f的棧框架。但是原來的和拷貝的對象都是對象引用,它們指向相同的內(nèi)存區(qū)域中的對象,因此可以修改。
In Java programming, it's common to say things
like "a String object is passed to method f" or "an array is passed to method g." Technically speaking, objects
and arrays are not passed. Instead, references or addresses to them are passed.
For example, if you have a Java object containing 25 integer fields, and each
field is 4 bytes, then the object is approximately 100 bytes long. But when you
pass this object as an argument to a method, there is no actual copy of 100
bytes. Instead, a pointer, reference, or address of the object is passed. The
same object is referenced in the caller and the called method. By contrast, in
a language like C++, it's possible to pass either an actual object or a pointer
to the object.
在java編程中,經(jīng)常可以聽到“一個String對象被傳遞到方法f”或者“一個數(shù)組被傳到方法g”。從技術(shù)上來說,不是傳遞一些對象或數(shù)組,而是傳遞引用或地址。例如,如果你有一個java對象包含25個整型的屬性,每個屬性占4 字節(jié),那這個對象大約是占100字節(jié)。但是作為形式參數(shù)傳遞的時候,不會拷貝100 字節(jié)。而是傳遞一個指針、引用或地址。這樣調(diào)用者和被調(diào)用的方法都指向同一個對象。對比而言,與C++類似的語言,它可以傳遞真實的對象,也可以傳遞該對象的指針。
What are the implications of pass-by-value? One
is that when you pass objects or arrays, the calling method and the called
method share the objects, and both can change the object. So you might want to
employ defensive copying techniques, as described in the September 4, 2001 Tech
Tip, "Making Defensive
Copies of Objects"
那什么是隱含的值傳遞呢?其一是當(dāng)你傳遞對象或數(shù)組,由調(diào)用方法和被調(diào)用方法共享,雙方都可修改對象。因此你可能想請入保護性拷貝技術(shù),就像2001年9月4 日技術(shù)貼士“使用保護性拷貝技術(shù)。”
You can fix the case above, where the called
method modifies an object, by making the class immutable. An immutable class is
one whose instances cannot be modified. Here's how you to do this:
你可以修正以上的例子,當(dāng)被調(diào)用方法修改一個對象時,讓這個類成為不可變的。一個不可變類是指其實例不可修改的類。這里教你怎樣去定義它。
1 final class A {
2
3 private final int x;
4
5 public A(int x) {
6 this.x = x;
7 }
8
9 public String toString() {
10 return Integer.toString(x);
11 }
12 }
13
14
15
16 public class CallDemo4 {
17
18 static void f(A arg1) {
19 //arg1.x = 10;
20 }
21
22 public static void main(String args[]) {
23 A arg1 = new A(5);
24 f(arg1);
25 System.out.println("arg1 = " + arg1);
26
27 }
28
29 }
The printed result is 5. Now uncomment the modification of A in f and recompile the program. Notice that it
results in a compile error. You have made A immutable, so it can't be legally modified by f.
打印結(jié)果是5,現(xiàn)在去掉CallDemo4中f方法內(nèi)的注釋和重新編譯。注意它會編譯出錯。因為你已經(jīng)讓A不可變了,所以f不能合法修改它。
Another implication of pass-by-value is that you
can't use method parameters to return multiple values from a method, unless you
pass in a mutable object reference or array, and let the method modify the
object. There are other ways of returning multiple values, such as returning an
array from the method, or creating a specialized class and returning an
instance of it.
另一個隱含的值傳遞是你不能利用方法的形參在一個方法中返回多個值,除非你傳入一個不可變對象引用或數(shù)組,然后讓方法修改該對象。還有另外的方式返回多個值,例如從方法中返回一個數(shù)組,或者創(chuàng)建一個特別定義的類的實例。
For more information about how arguments are
passed to Java Methods, see Section 1.8.1, Invoking a Method, and section
2.6.4, Parameter Values, in "The Java
Programming Language Third Edition" by Arnold, Gosling, and Holmes. Also see item
13, Favor immutability, and item 24, Make defensive copies when needed, in
"Effective Java
Programming Language Guide" by Joshua
想知道更多關(guān)于Java方法中的實際參數(shù)的傳遞的信息,請看Arnold, Gosling, and Holmes的《The Java
Programming Language Third Edition》中的1.8.1節(jié)“Invoking a Method”和2.6.4節(jié)“Parameter
Values”。還有Joshua的《Effective Java Programming Language
Guide》條款13 “Favor immutability”和24
“Make
defensive copies when needed”。
術(shù)語對照翻譯
英文
|
中文
|
Argument
|
實際參數(shù)(實參)
|
Parameter
|
形式參數(shù)(形參)
|
Method
|
方法
|
Function
|
函數(shù)
|
pass-by-value
|
值傳遞
|
pass-by-reference
|
引用傳遞
|
operand stack
|
操作數(shù)堆棧
|
Stack frame
|
棧框架
|
Caller
|
調(diào)用者
|
程序員的一生其實可短暫了,這電腦一開一關(guān),一天過去了,嚎;電腦一開不關(guān),那就成服務(wù)器了,嚎……