Java is Pass-by-Value, Dammit! 我靠!Java就是值传递!

原文地址:http://javadude.com/articles/passbyvalue.htm

关键点

  • 形如Object o这样的定义,我们实际上定义一个指针
  • 当我们把此指针传入方法形参,实则是把此指针所指向的地址传递给了方法形参,而不是对象本体的传入,也就是没有进行真正意义上的引用传递了。

全文翻译

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

我终于决定要写一点关于Java参数传递的东西了。我真的已经厌倦人云亦云的观点了——原始数据类型是值传递,对象是引用传递。

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

我有一颗像编译器样的心。术语“值传递”和“引用传递”的语义是有严格定义的。然而在大众谈论Java时,这两个术语总是遭到滥用。我想改变这种情况… 下面我就是我对此的解释:

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.

实参(或者变量表达式)已被完整计算,并且结果值被复制到了方法(或函数)执行时负责持有形参值的地方。这个地方通常是应用运行时栈的一块内存区域(Java就是这么处理的),但是其他语言可以选择别的参数存储方式。

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. Inhttp://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.4.1:

Java是严格意义上的值传递,如C一样。阅读下《Java语言规范》。其中对此已经讲的很清楚了这是正确的。原文如下:

When the method or constructor is invoked (�15.12), the values of the actual argument expressions initialize newly created parameter variables, each of the declared Type, before execution of the body of the method or constructor. The Identifier that appears in the DeclaratorId may be used as a simple name in the body of the method or constructor to refer to the formal parameter.

当一个方法或构造器被调用时,实参表达式的值会在执行方法或构造器的代码块前,初始化刚刚创建的参数变量,以及每个声明的类型。在方法或构造器的代码块中,DeclaratorID中出现的Identifier或许会作为一个简单的名字,从而去引用一个形参。

In the above, values is my emphasis, not theirs

上文中对值的加粗是我自己加的,不是原文的

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 ;)

简言之:Java具有指针,并且是严格意义上的值传递。没有其他杂七杂八的规定,就是这么简单、清晰、明了。(这就同类似邪恶C++的语法所允许的那样一样清晰;)

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?

你可以在此语言环境下写出一个标准的swap(a,b)方法(函数)嘛?

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

一个标准的swap方法(函数)有两个参数,它会将这两者互换。这样,传入方法里面的变量就可以改变方法外变量的值。它的基本结构如下:

// Figure 1: (Non-Java) Basic swap function structure 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

如果你可以在你语言环境下写出这样的方法(函数),那么这样的调用

// Figure 2: (Non-Java) Calling the swap functionType var1 = ...;Type var2 = ...;swap(var1,var2);

actually switches the values of the variables var1 and var2, the language supports pass-by-reference semantics.

就会从实际上交换变量var1和var2的值。前提是需要此语言支持引用传递

For example, in Pascal, you can write

例如,在Pascal中,你可以这样写

//Figure 3: (Pascal) Swap functionprocedure 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 := ...; { value "A" }    var2 := ...; { value "B" }     swap(var1, var2);    { now var1 has value "B" and var2 has value "A" }end;

or in C++ you could write

或者在C++中你可以这样写

//Figure 4: (C++) Swap functionvoid swap(SomeType& arg1, Sometype& arg2) {    SomeType temp = arg1;    arg1 = arg2;    arg2 = temp;}

...

SomeType var1 = ...; // value "A"SomeType var2 = ...; // value "B"swap(var1, var2); // swaps their values!// now var1 has value "B" and var2 has value "A"

(Please let me know if my Pascal or C++ has lapsed and I’ve messed up the syntax…)

(请让我知道我的Pascal和C++程序对不对,我已经搞混两者的语法了>_<)

But you cannot do this in Java!

但是你在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.

在Java中,对象是通过引用传递的,基本数据类型是值传递的

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.

这句话是半对的。人们都愿意承认非原始数据类型是值传递的;Java中没有这样一种指针(或引用)指向一个原始数据类型。

However, Objects are not passed by reference. A correct statement would be Object references are passed by value.

然而,对象也并非是引用传递的。正确的说法是对象引用是值传递的。

This may seem like splitting hairs, but 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

在Java中,我们以此为例:

//Figure 5: (Java) Pass-by-value example

public void foo(Dog d) {    d = new Dog("Fifi"); // creating the "Fifi" dog}

Dog aDog = new Dog("Max"); // creating the "Max" dog// at this point, aDog points to the "Max" dogfoo(aDog);// aDog still points to the "Max" dog

the variable passed in (aDog) is not modified! After calling foo, aDog still points to the “Max” Dog!

传入的变量(即 aDog)并没有被修改!,在调用foo方法后,aDog依旧指向“Max”Dog对象!

Many people mistakenly think/state that something like

很多人像这样错误的认为(或陈述)这样的东西:

//Figure 6: (Java) Still pass-by-value...

public void foo(Dog d) {     d.setName("Fifi");}

shows that Java does in fact pass objects by reference.

表明了Java事实上是通过引用传递对象的。

The mistake they make is in the definition of

他们犯的错误在于对下面定义代码的理解

// Figure 7: (Java) Defining a Dog pointer 

Dog d;

itself. When you write that definition, you are defining a pointer to a Dog object, not a Dog object itself.

当你写下这样的定义时,你就会创建了一个指向Dog对象的指针,而非一个Dog对象本身。

On Pointers versus References… 指针和引用的对比

The problem here is that the folks at Sun made a naming mistake.

问题在于Sun公司的家伙们制造了一个命名错误。

In programming language design, a “pointer” is a variable that indirectly tracks the location of some piece of data. The value of a pointer is often the memory address of the data you’re interested in. Some languages allow you to manipulate that address; others do not.

在编程语言的设计中,一个“指针”是一种变量。它间接追踪某些数据的位置。一个指针的值通常是你所感兴趣的数据的内存地址。一些语言允许你操纵这个地址,其他则非也。

A “reference” is an alias to another variable. Any manipulation done to the reference variable directly changes the original variable.

一个“引用”则是另一个变量的别名。任何对此引用的操作都将直接改变原始变量。

Check out the second sentence ofhttp://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.3.1.

看下这个网址的第二句话

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

引用的值(或者说就是引用)是这些对象的指针。而一个特别的空引用,它不指向任何对象

They emphasize “pointers” in their description… Interesting…

他们在他们的陈述中强调了“指针”一词,挺有意思~

When they originally were creating Java, they had “pointer” in mind (you can see some remnants of this in things like 
NullPointerException).

当他们在创作Java之初时,他们的脑海中是有“指针”这个概念的(你可以看到一些残存的迹象,如空指针异常)。

Sun wanted to push Java as a secure language, and one of Java’s advantages was that it does not allow pointer arithmetic as C++ does.

Sun公司希望把Java做成一个安全语音,并且Java优势之一就是它不允许像C++那样的指针运算。

They went so far as to try a different name for the concept, formally calling them “references”. A big mistake and it’s caused even more confusion in the process.

他们努力为此概念尝试使用一个不同名称,并在最终正式称其为“引用”。这是一个大失误,并且这在后来制造了更多的困惑。

There’s a good explanation of reference variables athttp://www.cprogramming.com/tutorial/references.html. (C++ specific, but it says the right thing about the concept of a reference variable.)

这个网址中有对引用变量的不错的解释。(虽然针对C++,但是它正确解释了引用变量的相关概念)

The word “reference” in programming language design originally comes from how you pass data to subroutines/functions/procedures/methods. A reference parameter is an alias to a variable passed as a parameter.

“引用”一词在编程语言设计中,最初来源于如何向子程序、函数、过程或方法传数据。一个引用参数是一个作为参数传递的变量的别名。

In the end, Sun made a naming mistake that’s caused confusion. Java has pointers, and if you accept that, it makes the way Java behaves make much more sense.

最后,Sun也就犯了一个命名错误,并且这个错误引起了不少困惑。Java具有指针,并且如果接受这个现实,这就会让Java变得更有意义了。

Calling Methods 方法调用

Calling

调用

//Figure 8: (Java) Passing a pointer by value

foo(d);

passes the value of d to foo; it does not pass the object that d points to!

这个方法时,会将d的值传入到foo中;它并不会传入d所指向的对象

The value of the pointer being passed is similar to a memory address. Under the covers it may be a tad different, but you can think of it in exactly the same way. The value uniquely identifies some object on the heap.

指针所传入的值类似内存地址。在底层中,两者有微小差别,但是你可认为两者完全相同。指针的值唯一标识堆中的一些对象。

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 (another poor choice in Java’s design; they should have used the same -> syntax for de-referencing as C++).

然而,这不耽误指针在底层的实现。在Java编程中,你可以完全像C或C++那样使用指针。就是语法上有点滴的不同(这又是Java设计中的一个软肋;设计者们本应像C++那样使用“->”作为解引用语法)

In Java,

在Java中,

//Figure 9: (Java) A pointer

Dog d;

is exactly like C++’s

完全像C++中的

//Figure 10: (C++) A pointer

Dog *d;

And using

指针的使用

//Figure 11: (Java) Following a pointer and calling a method

d.setName("Fifi");

is exactly like C++’s

完全像C++的

//Figure 12: (C++) Following a pointer and calling a method

d->setName("Fifi");

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.

总结下:Java有指针,并且传入的是指针的值。无法传入一个对象本身作为参数。你仅可以向对象传入一个指针。

Keep in mind, when you call

记住,当你调用

//Figure 13: (Java) Even more still passing a pointer by value

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 seehttp://www-106.ibm.com/developerworks/library/j-praxis/pr1.html. It’s from Peter Haggar’s excellent book, Practical Java.)

对于此问题的一个轻微的不同之处(但是依旧是正确的),请参阅网址内容。内陆来自Peter hagger的一本经典之作《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相关术语的常用意义。注意,这并非严格意义上的“值传递”和“引用传递”语义:

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

这并非完全正确地使用了值传递;我个人认为这应该被称为一种类似“记忆传递”的东西。(更多内容,请参阅Gamma等人编著的《设计模式》中关于记忆模式的表述)

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

这并非是对“引用传递”完全正确的使用(再一次强调,你无法改变实参本身)。我认为这样叫做“代理传递”样的东西(再次推荐阅读《设计模式》中对代理模式的讲解)

Follow up from stackoverflow.com 在stackoverflow.com上跟进问题

I posted the following as some clarification when a discussion on this article arose on http://stackoverflow.com.

当在stackoverflow上讨论此文时,我贴出了如下内容以作说明:

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

《Java规范》说Java中的任何传递都是值传递。在Java中没有“引用传递”一说。

The key to understanding this is that something like

理解此的关键,就是要理解下面这样的代码:

//Figure 14: (Java) Not a Dog; a pointer to a Dog

Dog myDog;

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

并不代表一个Dog对象;它实际上是执行Dog对象的的一个指针。

What that means, is when you have

它表达的意思是,当你有

// Figure 15: (Java) Passing the Dog's location

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 b/c java pointers aren’t direct addresses, but it’s easiest to think of them that way)

实际上你正在向foo方法中传递创建的Dog对象的地址。(我认为本质上Java指针并非是直接地址,但是这么想会让理解更容易一些)

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

假设Dog对象驻扎在内存地址42中。这意味着我们向方法传入42。

If the Method were defined as

如果这个方法没定义成这样

//Figure 16: (Java) Looking at the called method in detail

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

参数someDog被设置为42。

at line “AAA” 在“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

someDog指向这个Dog对象(也就是在地址42中存储的Dog对象),并且这个对象要求将名字改为Max。

at line “BBB” 在“BBB”行

a new Dog is created. Let’s say he’s at address 74 we assign the parameter someDog to 74

一个新的Dog对象被创建。让我们假设它在内存地址74当中。我们将74传入someDog对象中。

at line “CCC” 在“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

someDog现在指向了地址为74的Dog对象,现在这个位于地址74的Dog对象要求将名字改为Rowlf,然后我们的方法返回了。

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

现在让我们想想在方法外发生了啥:

Did myDog change? myDog发生改变了嘛?

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.

记住myDog 是一个指针,并非一个真实的Dog对象。答案是“没有”。myDog依旧是42;依然指向了原来的Dog对象。

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.

Java 非常像C。你可以为一个指针赋值,也可把指针传入到一个方法中去,还可以使用方法中的指针改变它所指向的内容。然而,你无法改变指针的指向。

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

在C++,Ada,Pascal以及其他支持引用传递的语言中,你可以对传入的变量进行实际上的改变。

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.

如果Java支持“引用传递”的概念,那么我们在上面定义的这个foo方法将会在BBB行,当我们把它赋值给someDog时,改变myDog的指向。

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.

想想作为传入变量别名的引用参数。当我们分配这个别名时,那么这个别名实际上就是指我们传入的变量。

Java is Pass-by-Value, Dammit! 我靠!Java就是值传递!相关推荐

  1. Java方法01 方法(函数)定义、调用、值传递、重载、命令行传递参数

    Java 方法的定义.调用.值传递.重载.命令行传递参数 1. 什么是方法? 2. 方法的定义和调用 3. 值传递和引用传递 4. 方法的重载 5. 命令行传递参数 6. 可变参数(输入参数的数量不确 ...

  2. java形参和实参的三种传递方式(值传递,地址传递,引用传递)

    博客 博客 值传递:对形参的修改不会影响到实参 引用传递:对形参的修改会影响实参 一.值传递 在主函数中 #include <iostream> using namespace std;v ...

  3. java 数组传递是引用吗_数组是通过值传递还是通过Java引用传递?

    本问题已经有最佳答案,请猛点这里访问. Possible Duplicate: Is Java"pass-by-reference"? 数组不是Java中的原始类型,但它们也不是对 ...

  4. java对象引用出错_“Java有值传递和引用传递”为什么错了?

    前言 初学Java的时候,老师在课堂上说"Java有值传递和引用传递",但网上"Java只有值传递"的呼声很高. 本人在查找资料的过程中,在这两个说法之间反复横 ...

  5. 爆肝3万5千字的Java学习笔记(超详细的java)

    Java学习 java入门 java三大版本 javaSE:标准版(桌面程序,控制台开发-) javaME:嵌入式开发(手机,小家电-) javaEE:E企业级开发(web端,服务器开发-) Java ...

  6. intbyreference java_从内存出发,java是pass by value 还是pass by reference

    如果有人问你,java到底是pass by value还是pass by reference, 你一定要先斩钉截铁的说,java is pass by value. 我们先看一个简单的例子 publi ...

  7. java python算法_用Python,Java和C ++示例解释的排序算法

    java python算法 什么是排序算法? (What is a Sorting Algorithm?) Sorting algorithms are a set of instructions t ...

  8. Stack Overflow上188万浏览量的提问:Java 到底是值传递还是引用传递?

    来自:沉默王二 在逛 Stack Overflow 的时候,发现了一些访问量像阿尔卑斯山一样高的问题,比如说这个:Java 到底是值传递还是引用传递?访问量足足有 188万+,这不得了啊!说明有很多很 ...

  9. 为什么说 Java 中只有值传递?

    对于初学者来说,要想把这个问题回答正确,是比较难的.在第二天整理答案的时候,我发现我竟然无法通过简单的语言把这个事情描述的很容易理解,遗憾的是,我也没有在网上找到哪篇文章可以把这个事情讲解的通俗易懂. ...

最新文章

  1. c语言中external,static关键字用法
  2. 构造函数(包含this关键字的简单应用)
  3. redis入门及java操作
  4. 教你打造 Android 中的 IOC 框架
  5. SSH实现分页查询(转)
  6. 六、乘胜追击,将剩下的Git知识点搞定
  7. 14.6.3.1 The InnoDB Buffer Pool
  8. html设置页面编码gbk,GBK及UTF-8网页编码定义与应用
  9. 学习Java编程-Java Timezone类常见问题
  10. C#LeetCode刷题-拓扑排序
  11. Termux配置ssh连接
  12. oa中获取当前用户信息
  13. Gym - 102163M
  14. LiveData源码解析
  15. matlab生成的图片有边,科学网—图片空白边缘处理/统计直方图---matlab/保存生成高质量的清晰图 - 杨小林的博文...
  16. 【CyberSecurityLearning 12】数据链路层 及 交换机工作原理与配置
  17. 用计算解决科学难题,用算法让生活变得更好
  18. 如何破解软件狗,dongle
  19. 【数据结构】字符串 模式匹配算法的理解与实现 Brute Force算法(BF算法)与KMP算法 (C与C++分别实现)
  20. 网络安全基础——Linux基础

热门文章

  1. 非常详细的Series核心操作使用详解
  2. ocp认证考试报名_OCP最新报名考证流程
  3. shapenet数据集_三维形状数据的深度特征表示
  4. 《只是为了好玩:Linux之父林纳斯自传》
  5. 两个坐标系转换的变换矩阵
  6. PCB线路板表面处理工艺的优缺点合集
  7. NOIP2016 天天爱跑步 线段树合并
  8. python爬虫实践之爬取豆瓣高评分电影
  9. Python进阶-高级语法
  10. java基础之包_繁星漫天_新浪博客