`
Eastsun
  • 浏览: 308885 次
  • 性别: Icon_minigender_1
  • 来自: 天津
社区版块
存档分类
最新评论

澄清:Java中只有按值传递,没有按引用传递!

阅读更多
  前言:在JAVA面试题解惑系列(五)——传了值还是传了引用?中作者提到了“JAVA中的传递都是值传递吗?有没有引用传递呢? ”这个问题,最终得到:
引用
最后我们得出如下的结论:

   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).
分享到:
评论
13 楼 summerfeel 2008-08-08  
1.基本数据类型按值传递
2.对象类型按引用传递,只不过不是直接把指向对象的引用传递过去,而是再复制一个引用,将复制的引用作为参数传递
12 楼 卡拉阿风 2008-08-06  
java程序语言设计总是使用传值调用。
但当方法的参数类型是对象或数组等引用类型时,在方法调用中传递给该参数的仍然是调用程序中对应的变量的值,即对某个对象或数组的引用
11 楼 sea7 2008-08-05  
其实关键看你对引用怎么理解了
10 楼 H_eaven 2008-08-04  
引用,值.
A a         //a 是一个引用(会在栈上分得空间).
= new A(); //new A()这是一个值(一个对象会在内存堆上分得空间).

test(A arg){
}
就算你调用test(new A()),它在执行过程也是有名的:arg.
只是你传入了一个匿名的值.

9 楼 rxgp02a 2008-07-15  
这个没什么好争论的吧,不管你传的是什么,传过去的都只是一个副本而已,这个副本作为方法的局部变量保存在栈中。
如果传的是基本数据类型,修改这个值并不会影响作为参数传进来的那个变量,因为你修改的是方法的局部变量,是一个副本。
如果传的是一个对象的引用,也是一样的,也是一个副本,但是这个副本和作为参数传进来的那个引用指向的是内存中的同一个对象,所以你通过这个副本也可以操作那个对象。但是如果你修改这个引用本身,比如让他指向内存中的另外一个对象,原来作为参数传进来的那个引用不会受到影响。
我觉得弄明白这些就行了,说值传递或引用传递都无所谓,但是说值传递更适合一些,这个值可以是引用也可以是基本数据类型。
8 楼 xiao0556 2008-07-15  
在传递引用的时候其实是复制了一份引用传进去的.
A a=new A();
test(a)
相当于
(A b=a;
test(b)
)

7 楼 williamy 2008-07-15  
引用到底是什么?
Java这些概念的东西,最头痛了,看C++时候,什么都很轻松,但是看Java时候,郁闷了死了
引用是指针吗?
6 楼 MarkDong 2008-07-15  
Java中的String、Integer等类型都是不可变类型,所以把这样的人传入方法内部,那么就无法像楼上说得,换个胳膊换个腿儿的,因为那是不可变的人。
而我们自己写的Bean就不同了,那是可变的机器人,传到方法内部一通Set后,面目全非了。当然了,你无法把A机器人换成B机器人,但是你可以让A机器人和B机器人看起来一样。
呵呵,听起来有点儿绕了,A机器人的头、身体、胳臂、腿儿都set成B机器人的了,但是A机器人也不是B机器人。因为它们占用的空间不一样。
5 楼 welcomyou 2008-07-14  
MarkDong 写道
楼主把C++的例子理解错误了,那个swap(Type& arg1, Type& arg2)方法,交换的是arg1和arg2两个地址指向的内容,而不是arg1和arg2本身。

说白了,就是把arg1的爹传进来了,而不是arg1本身,这个爹呢,可以在生个儿子,或者直接认个新儿子。但是Java不会,传进去后,可能卸个胳膊加个腿,但是人呢还是那个人,我理解的对吧
4 楼 zlfoxy 2008-07-14  
是不是可以这么理解:大家对于“按引用传递”这个概念没有达成一个统一的共识才导致这么多分歧?
3 楼 MarkDong 2008-07-14  
楼主把C++的例子理解错误了,那个swap(Type& arg1, Type& arg2)方法,交换的是arg1和arg2两个地址指向的内容,而不是arg1和arg2本身。
2 楼 Eastsun 2008-07-14  
归根究底,其实就是一个对“按引用传递”这个概念理解的问题。
如果你非得说“按引用传递”,那么得重新定义一个与C++中“按引用传递”不同的概念出来。
1 楼 MarkDong 2008-07-14  
楼主的说法有点儿牵强,所有的调用都是按值传递这是没错的,因为调用堆栈的原理限定了我们只能将各种值压入堆栈,而方法返回时,并不会将堆栈中的值再进行处理,而只是简单的调整栈顶指针将原先压入堆栈的值废弃掉。所以,一切对压栈而传递到方法体内的参数,方法内部所做的修改对外界都是无法看到的。
那么鉴于这种情况,为了将函数内部对参数的修改可以带到函数外,各种语言做了不同的处理,C/C++中可以传递指针,而Java则默认传递对象的引用。如果楼主非要把方法调用时压入堆栈的地址称为值的话,其实也并非不可,只不过这种说法我觉得有点儿牵强了。

相关推荐

    浅谈Java中真的只有值传递么

    Java中的参数传递方式一直以来都是一个讨论的热点,尤其是在学习编程初期,值传递和引用传递的概念可能会引起混淆。本文将深入探讨Java中参数传递的实质,以帮助理解和澄清这一问题。 首先,我们需要理解值传递和...

    深入解析Java编程中方法的参数传递

    在Java中,函数参数的传递方式只有按值传递一种,而不是像某些其他语言那样有按引用传递。这可能会引起一些初学者的困惑,因为Java中的对象参数传递实际上看起来像是按引用传递,但实际上并不完全如此。 首先,让...

    90个高质量的java问答.pdf

    - **引用与值的区别**:澄清字符串是按值传递还是按引用传递的概念。 - **字符串不可变性**:解释字符串的不可变性是如何影响其传递行为的。 - **字符串池**:探讨字符串常量池在字符串传递中的作用。 #### 15. ...

    计算机专业论文译文-对象的传递与返回

    首先,让我们澄清一点:当我们在Java中“传递”一个对象时,实际上我们传递的是对象的引用,而不是对象本身。这与某些编程语言(如C++)中的行为有所不同,C++允许直接操作指针,而Java则限制了这种操作。Java中的每...

    臧圩人--JAVA面试题解惑系列合集.pdf

    - **知识点**:澄清Java中参数传递的机制,即所有参数都是按值传递,但对于对象而言,传递的是指向该对象的引用的值,而非对象本身。 **1.6 JAVA面试题解惑系列(六)——字符串(String)杂谈** - **知识点**:...

    Java核心技术编程第8版(英文版)

    - **方法参数**:讨论了如何传递参数到方法中,包括按值传递和按引用传递的区别。 - **对象构造**:介绍了对象的创建过程,以及构造函数的使用。 - **包**:介绍了包的概念及其在组织类方面的作用。 - **类路径**:...

    JavaJava%BEE软件工程师就业求职手册.doc

    - **4.2.1 理解参数传递**:解释了Java中参数传递的基本机制,包括值传递和引用传递的概念。 - **4.2.2 注意final参数传递**:探讨了在方法签名中使用`final`关键字的含义及其影响。 - **4.3 类型转换** - **...

    JVM性能调优-JVM内存整理及GC回收.pdf

    首先,我们要澄清Java中的参数传递机制。Java中,无论是基本类型还是对象引用,都在运行时栈中按值传递。这意味着,即使是对象引用,也只是引用的副本被传递,而不是实际的对象。当在被调用方法中修改对象的属性时,...

    java学习笔记

    在开始之前,首先需要澄清标题“java学习笔记”和描述“Java笔记前辈个人经验,以便于程序员得到更多的经验。能更好的学习Java,更深入的了解Java。”以及提供的部分内容,似乎并不直接关联。内容片段看起来像是一本...

    Java语言程序设计 郑莉 王行言 马素霞 课后习题答案

    《Java语言程序设计》是由郑莉、王行言和马素霞三位专家共同编著的一本深入探讨Java编程的教材。这本书涵盖了从基础到高级的Java编程知识,并且每个章节都配备了丰富的课后习题,旨在帮助读者巩固所学概念并提升实践...

    Android 编程 andbook 中文版

    - **findViewById()方法**:用于从XML布局文件中获取视图的引用。 - **为View处理点击**: - **监听器**:设置点击事件监听器,当用户点击时执行相应的操作。 - **从子Activity返回数值**: - **setResult()方法*...

    2021-2022计算机二级等级考试试题及答案No.19030.docx

    - **知识点**: Java 中的对象赋值实际上是指向同一内存地址的引用。 - **误解澄清**: - 在 Java 中,对象赋值并不生成新的对象,而是让两个变量指向同一个对象。 ### 11. C 语言中的字符型常数 - **知识点**: C ...

    zk developeguide3.5.pdf

    - **在`zscript`中实现Java类**:说明了如何在ZK脚本中调用Java类。 - **`forward`属性**:介绍了页面跳转的机制。 - **手动创建组件**:提供了不使用ZUML而直接创建组件的方法。 - **为特定页面定义新组件**:介绍...

    2021-2022计算机二级等级考试试题及答案No.10913.docx

    这意味着,如果没有明确指出函数的返回类型,如`float`、`double`或`void`等,那么该函数会被认为返回一个整数值。 ### 2. 内存概念的澄清 - **知识点**: 扩展内存指的是增加RAM(随机存取内存),而非ROM(只读...

    2021-2022计算机二级等级考试试题及答案No.9111.docx

    - **异常类型**:在Java JDBC编程中,常见的异常是`SQLException`,它是处理数据库操作错误的主要异常类。 #### 19. 建立良好程序设计风格的原则 **知识点解析:** - **基本原则**:良好的程序设计风格强调代码的...

    2021-2022计算机二级等级考试试题及答案No.18782.docx

    - **正确的类声明格式**:在Java中,正确的类声明应该包括`public class ClassName { ... }`这样的格式。 ### 11. Access2003数据表管理 - **删除字段**:在Access2003中,如果字段已经与其他表建立了关系,则需要...

    android摘要

    在Java编程中,我们通常有多种方式在类之间传递数据,例如通过成员变量、构造函数或者公共方法。但对Activity而言,由于它们是由框架动态创建和管理的,我们无法直接获取到它们的实例引用,这就限制了上述方法的直接...

Global site tag (gtag.js) - Google Analytics