Topic: JAVA面试题解惑系列(五)——传了值还是传了引用?

  Print this page

1.JAVA面试题解惑系列(五)——传了值还是传了引用? Copy to clipboard
Posted by: 臧圩人
Posted on: 2008-07-13 16:55

------------------------------------------------------------------------------------
我想出一本名为《JAVA面试题解惑系列》的书籍,详情请见:
http://rmyd.group.javaeye.com/group/topic/6193
目前网络连载中:http://zangweiren.javaeye.com/
请大家多关注,多提宝贵意见!
------------------------------------------------------------------------------------

作者:臧圩人(zangweiren)
网址:http://zangweiren.javaeye.com

>>>转载请注明出处!<<<

JAVA中的传递都是值传递吗?有没有引用传递呢?

在回答这两个问题前,让我们首先来看一段代码:
Java代码
public class ParamTest {
  // 初始值为0
  protected int num = 0;

  // 为方法参数重新赋值
  public void change(int i) {
    i = 5;
  }

  // 为方法参数重新赋值
  public void change(ParamTest t) {
    ParamTest tmp = new ParamTest();
    tmp.num = 9;
    t = tmp;
  }

  // 改变方法参数的值
  public void add(int i) {
    i += 10;
  }

  // 改变方法参数属性的值
  public void add(ParamTest pt) {
    pt.num += 20;
  }

  public static void main(String[] args) {
    ParamTest t = new ParamTest();

    System.out.println("参数--基本类型");
    System.out.println("原有的值:" + t.num);
    // 为基本类型参数重新赋值
    t.change(t.num);
    System.out.println("赋值之后:" + t.num);
    // 为引用型参数重新赋值
    t.change(t);
    System.out.println("运算之后:" + t.num);

    System.out.println();

    t = new ParamTest();
    System.out.println("参数--引用类型");
    System.out.println("原有的值:" + t.num);
    // 改变基本类型参数的值
    t.add(t.num);
    System.out.println("赋引用后:" + t.num);
    // 改变引用类型参数所指向对象的属性值
    t.add(t);
    System.out.println("改属性后:" + t.num);
  }
}

这段代码的运行结果如下:

参数--基本类型
原有的值:0
赋值之后:0
运算之后:0

参数--引用类型
原有的值:0
赋引用后:0
改属性后:20

从上面这个直观的结果中我们很容易得出如下结论:

对于基本类型,在方法体内对方法参数进行重新赋值,并不会改变原有变量的值。
对于引用类型,在方法体内对方法参数进行重新赋予引用,并不会改变原有变量所持有的引用。
方法体内对参数进行运算,不影响原有变量的值。
方法体内对参数所指向对象的属性进行运算,将改变原有变量所指向对象的属性值。

上面总结出来的不过是我们所看到的表面现象。那么,为什么会出现这样的现象呢?这就要说到值传递和引用传递的概念了。这个问题向来是颇有争议的。

大家都知道,在JAVA中变量有以下两种:

基本类型变量,包括char、byte、short、int、long、float、double、boolean。
引用类型变量,包括类、接口、数组(基本类型数组和对象数组)。

当基本类型的变量被当作参数传递给方法时,JAVA虚拟机所做的工作是把这个值拷贝了一份,然后把拷贝后的值传递到了方法的内部。因此在上面的例子中,我们回头来看看这个方法:
Java代码
// 为方法参数重新赋值
public void change(int i) {
i = 5;
}

在这个方法被调用时,变量i和ParamTest型对象t的属性num具有相同的值,却是两个不同变量。变量i是由JAVA虚拟机创建的作用域在change(int i)方法内的局部变量,在这个方法执行完毕后,它的生命周期就结束了。在JAVA虚拟机中,它们是以类似如下的方式存储的:

[img]http://zangweiren.javaeye.com/upload/picture/pic/17754/bf858157-cd1b-3e9e-9fd1-374906bfd90f.jpg[/img]

很明显,在基本类型被作为参数传递给方式时,是值传递,在整个过程中根本没有牵扯到引用这个概念。这也是大家所公认的。对于布尔型变量当然也是如此,请看下面的例子:
Java代码
public class BooleanTest {
  // 布尔型值
  boolean bool = true;

  // 为布尔型参数重新赋值
  public void change(boolean b) {
    b = false;
  }

  // 对布尔型参数进行运算
  public void calculate(boolean b) {
    b = b && false;
    // 为了方便对比,将运算结果输出
    System.out.println("b运算后的值:" + b);
  }

  public static void main(String[] args) {
    BooleanTest t = new BooleanTest();

    System.out.println("参数--布尔型");
    System.out.println("原有的值:" + t.bool);
    // 为布尔型参数重新赋值
    t.change(t.bool);
    System.out.println("赋值之后:" + t.bool);

    // 改变布尔型参数的值
    t.calculate(t.bool);
    System.out.println("运算之后:" + t.bool);
  }
}

输出结果如下:

参数--布尔型
原有的值:true
赋值之后:true
b运算后的值:false
运算之后:true

那么当引用型变量被当作参数传递给方法时JAVA虚拟机又是怎样处理的呢?同样,它会拷贝一份这个变量所持有的引用,然后把它传递给JAVA虚拟机为方法创建的局部变量,从而这两个变量指向了同一个对象。在篇首所举的示例中,ParamTest类型变量t和局部变量pt在JAVA虚拟机中是以如下的方式存储的:

[img]http://zangweiren.javaeye.com/upload/picture/pic/17756/3c3c237f-bd06-3e7a-a0d9-a2317754b560.jpg[/img]

有一种说法是当一个对象或引用类型变量被当作参数传递时,也是值传递,这个值就是对象的引用,因此JAVA中只有值传递,没有引用传递。还有一种说法是引用可以看作是对象的别名,当对象被当作参数传递给方法时,传递的是对象的引用,因此是引用传递。这两种观点各有支持者,但是前一种观点被绝大多数人所接受,其中有《Core Java》一书的作者,以及JAVA的创造者James Gosling,而《Thinking in Java》一书的作者Bruce Eckel则站在了中立的立场上。

我个人认为值传递中的值指的是基本类型的数值,即使对于布尔型,虽然它的表现形式为true和false,但是在栈中,它仍然是以数值形式保存的,即0表示false,其它数值表示true。而引用是我们用来操作对象的工具,它包含了对象在堆中保存地址的信息。即使在被作为参数传递给方法时,实际上传递的是它的拷贝,但那仍是引用。因此,用引用传递来区别与值传递,概念上更加清晰。

最后我们得出如下的结论:

1、基本类型和基本类型变量被当作参数传递给方法时,是值传递。在方法实体中,无法给原变量重新赋值,也无法改变它的值。
2、对象和引用型变量被当作参数传递给方法时,在方法实体中,无法给原变量重新赋值,但是可以改变它所指向对象的属性。至于到底它是值传递还是引用传递,这并不重要,重要的是我们要清楚当一个引用被作为参数传递给一个方法时,在这个方法体内会发生什么。

2.Re:JAVA面试题解惑系列(五)——传了值还是传了引用? [Re: 臧圩人] Copy to clipboard
Posted by: billgacsli
Posted on: 2008-07-14 19:49

发点小看法
Thank in Java中指出了JAVA中只有值传递,没有引用传递

关键得弄清楚什么是引用传递?什么是值传递?判断准则是什么?

判断准则是:实参和形参的关系。如果形参是实参的拷贝,称为值传递;否则,如果形参是实参的引用,则称为引用传递。通过引用传递,改变形参的值将会影响实参;而通过值传递,改变形参的值不会影响实参。不能根据实参是引用就认定为引用传递,也就是要区分传递引用与引用传递。

详见MyBlog:http://blog.programfan.com/article.asp?id=36710

3.Re:JAVA面试题解惑系列(五)——传了值还是传了引用? [Re: 臧圩人] Copy to clipboard
Posted by: 臧圩人
Posted on: 2008-07-14 22:03

我想问一下楼上的朋友,你的定义是从哪里获得的?或者是你自己的理解?

4.Re:JAVA面试题解惑系列(五)——传了值还是传了引用? [Re: 臧圩人] Copy to clipboard
Posted by: billgacsli
Posted on: 2008-07-15 09:28

算是我自己的理解,我试图找了下定义,也没找到个确定的,嘿嘿

5.Re:JAVA面试题解惑系列(五)——传了值还是传了引用? [Re: 臧圩人] Copy to clipboard
Posted by: JiafanZhou
Posted on: 2008-07-15 16:11

Hi,

I spent some time this morning reading this article and try to understand this article here, I found couple of issues.

- First, the example you provided is not very intuitive, i.e. really should put some text in the output. I have been trying scrolling down and up to link the targeted output.

- The language you use here is not very understandable, I heard you mentioned you wished to publish your own book about interviewing questions for Java programmers. I think you need to rewrite this article.
To be more specific, I try very hard to understand the following sentence:
方法的情况下,变量i和ParamTest型对象t的属性num具有相同的值,却是两个不同变量。变量i是由JAVA虚拟机创建的作用域在change(int i)方法内的局部变量,在这个方法执行完毕后,它的生命周期就结束了。
1. What do you mean by "方法的情况下"?
2. You haven't explained the "JVM作用域" before, people will be lost.
3. As a general, I understand this sentence but it is very bad structured.

- The images you attached here are not displayed, so I have no idea what you try to explain in those images.

- The code below, using an "&" sign, is that correct?
b &= false; 


- 最后我们得出如下的结论:

基本类型和基本类型变量被当作参数传递给方法时,是值传递。在方法实体中,无法给原变量重新赋值,也无法改变它的值。
对象和引用型变量被当作参数传递给方法时,是引用传递。在方法实体中,无法给原变量重新赋值,但是可以改变它所指向对象的属性。

Ok, this is *completely wrong*. Again, in Java, there is only Pass-by-value, there is *no* concept of pass-by-reference at all. The definition by "pass-by-reference" means the original object reference is passed into the method (like in C++), however, in Java, it passes a deep copy of the original object reference. Notice it is still "pass-by-value" although it is an object reference. Otherwise you have completely re-defined the Java programming language. (Passing the original object reference, and passing a copy of it are totally difference. ) The reason for this is provided as below:

Imagine that in an application that you have 100,000 method invocations which passing the same object reference, in C++ (could define as pass-by-reference), there will be only an single instance of it created in stack. Whereas in Java (pass-by-value) means 100,000 instances(values) will be generated accordingly in stack.

So as a general, you might consider to rewrite this article if possible.

Regards,
Jiafan

6.Re:JAVA面试题解惑系列(五)——传了值还是传了引用? [Re: 臧圩人] Copy to clipboard
Posted by: 臧圩人
Posted on: 2008-07-15 17:37

回复HenryShanley:
非常感谢你的回复,你的建议很中肯,对我也很有帮助!

下面我就你提出的疑问做一下回应。

1、你的建议很好,我已经改善了我的例子程序。
2、“方法的情况下”不是一段话的开头,而是连着前面的句子的,只是被一个方法的定义隔开了,你连着读就通顺了。
3、关于为什么没有解释“作用域”的概念。我写的这一系列文章是以面试题为出发点,讲解JAVA的基础知识,尽量做到清晰和深入。但是与JAVA入门教材的出发点不同,而是更多地针对面试题,因此不会涵盖所有JAVA基础内容,比如什么叫变量、for语句如何使用、怎样定义数组这些都不会包含。
4、我在浏览这篇帖子时,图片是可以正常显示的,不知你那里是不是网络慢的原因?
5、b &= false;类似于i+=2;,可以编译并执行,你所指的错误是什么呢?
6、回到这篇文章最核心的问题,值传递和引用传递的概念。

我觉得区分引用传递不应该从传了引用本身还是引用的拷贝来区分。值是什么呢?我觉得理解为数值更为准确。引用不是值,引用的拷贝还是引用,也不是值。就像对象不是值,对象的拷贝还是对象,而不是值。我们不能但从拷贝这一个动作来区分值和引用的概念。

因此,我觉得用引用传递来区别与值传递,概念上更加清晰。

7.Re:JAVA面试题解惑系列(五)——传了值还是传了引用? [Re: 臧圩人] Copy to clipboard
Posted by: billgacsli
Posted on: 2008-07-16 15:05

不知道LZ有没仔细看我的bolg
LZ给出一个确切地“值传递”和“引用传递”的准确定义,或者说其区分准则是什么?这样讨论起来才好说,要不然很难说明白。这种概念性讨论最基本的就是先弄清楚定义,要不然很容易鸡同鸭讲。

所谓参数传递是将实参的“值”传递给形参
不知道LZ怎么理解“值”、“数值”、“引用”

JAVA中是分堆和栈的,对象是在堆中生成,其引用存在栈中,也就是说引用其实是一个变量,存储的是“地址”(在Java中称为引用),很类似C++中的指针,莫非“地址”(引用)就不是“值”了??不知从何说起。从这个意义上说,java中传递引用时,实际上是将实参(存的是地址)的值拷贝给形参,也就是值复制。

在C++中的“引用传递”,是将变量a作为实参传递时,实际传递的是a的地址。所以才称为引用。在C++中当变量的地址&a作为实参传递时,并不能说是引用传递,而是值传递(这点应该没什么疑问吧?)。

8.Re:JAVA面试题解惑系列(五)——传了值还是传了引用? [Re: 臧圩人] Copy to clipboard
Posted by: JiafanZhou
Posted on: 2008-07-16 16:05

臧圩人 wrote:
2、“方法的情况下”不是一段话的开头,而是连着前面的句子的,只是被一个方法的定义隔开了,你连着读就通顺了。

The reason that I have been so strict on this is because we need to make sure all levels of Java programmers can understand your words without any problems. And we should really avoid that happening. (i.e. we should never have our paragraphs slitted by some code. What happened if the code is really long?? People will get lost.

臧圩人 wrote:
3、关于为什么没有解释“作用域”的概念。我写的这一系列文章是以面试题为出发点,讲解JAVA的基础知识,尽量做到清晰和深入。但是与JAVA入门教材的出发点不同,而是更多地针对面试题,因此不会涵盖所有JAVA基础内容,比如什么叫变量、for语句如何使用、怎样定义数组这些都不会包含。

Again, it is really a mistake that talking about some terminologies without mentioning it first. Or you should simply mention something like "这篇文章假设读者已经对‘什么叫变量、for语句如何使用、怎样定义数组,和Java作用域’这些概念了解了“. In that case, people who do not understand those terms can look up for them first. Also you push the responsibilities into readers.

臧圩人 wrote:
4、我在浏览这篇帖子时,图片是可以正常显示的,不知你那里是不是网络慢的原因?

Now, up to today. 16/07/2008, I can see the first image, but the second image is still not displayed

臧圩人 wrote:
5、b &= false;类似于i+=2;,可以编译并执行,你所指的错误是什么呢?

Why do you use b &= false????????? I have never seen some code like that to assign a boolean value to a variable. Maybe it can pass the compiler, but it is really confusing.

Can somebody else give comments on this code? Maybe I am out of style.


6、回到这篇文章最核心的问题,值传递和引用传递的概念。

我觉得区分引用传递不应该从传了引用本身还是引用的拷贝来区分。值是什么呢?我觉得理解为数值更为准确。引用不是值,引用的拷贝还是引用,也不是值。就像对象不是值,对象的拷贝还是对象,而不是值。我们不能但从拷贝这一个动作来区分值和引用的概念。

因此,我觉得用引用传递来区别与值传递,概念上更加清晰。

By definition, all text books define Java as only "passing-by-value". I totally understand what you explained here and they are right. BUT.... you cannot say Java pass parameter as "pass-by-reference", the reason I have explained in my last comment.

Also, if I was the interviewer and I heard some interviewee said "Java passes method parameters by reference". Immediately I will raise a red flag in my mind to question about "Is he/she really understanding Java?" Because it is technically wrong.

Jiafan

9.Re:JAVA面试题解惑系列(五)——传了值还是传了引用? [Re: 臧圩人] Copy to clipboard
Posted by: 臧圩人
Posted on: 2008-07-16 21:26

回复HenryShanley:

再次感谢你的回复。

1、已经调整为更为通顺的句子了。
2、这一点我们的看法是一致的,在这个系列做合集的时候,我会放在诸如前言之类的部分中说明。
3、我也说不清是什么原因,不过我这里一直能显示。
4、已经更改为更容易理解的b = b && false;了。

10.Re:JAVA面试题解惑系列(五)——传了值还是传了引用? [Re: 臧圩人] Copy to clipboard
Posted by: 臧圩人
Posted on: 2008-07-16 21:30

回复billgacsli & HenryShanley:

关于值传递和引用传递的问题,我认同Bruce Eckel在《Thinking in Java》第三版中的观点:

In the end, it isn’t that important—what is important is that you understand that passing a reference allows the caller’s object to be changed unexpectedly.

11.Re:JAVA面试题解惑系列(五)——传了值还是传了引用? [Re: 臧圩人] Copy to clipboard
Posted by: JiafanZhou
Posted on: 2008-07-17 15:53

臧圩人 wrote:
回复billgacsli & HenryShanley:

关于值传递和引用传递的问题,我认同Bruce Eckel在《Thinking in Java》第三版中的观点:
In the end, it isn’t that important—what is important is that you understand that passing a reference allows the caller’s object to be changed unexpectedly.

Again, Bruce's sentence is very very misleading. I want to put an end to this topic because it is a trivial issue and it is not that important. (The more critical thing is that you understand it)

The below link shows that Java handles Object by reference but passing them by value.

http://www.javaworld.com/javaworld/javaqa/2000-05/03-qa-0526-pass.html

12.Re:JAVA面试题解惑系列(五)——传了值还是传了引用? [Re: 臧圩人] Copy to clipboard
Posted by: 臧圩人
Posted on: 2008-07-17 17:14

Thumbs up

13.Re:JAVA面试题解惑系列(五)——传了值还是传了引用? [Re: 臧圩人] Copy to clipboard
Posted by: JiafanZhou
Posted on: 2008-07-17 18:00

Hi, 臧圩人

Can you take all the comments here in this thread into consideration and modify your existing article if possible? (Because there are quite a lot of misleading and confusing concepts related to pass-by-value and pass-by-reference).

I see your efforts of updating this document, but I am still not happy about the outcome. Nonetheless, those concepts need to be explained in a non-ambiguous way. (some context in the original article is still not intuitive for others). After that, I can give you points according to your update.

Thanks,
Jiafan

14.Re:JAVA面试题解惑系列(五)——传了值还是传了引用? [Re: 臧圩人] Copy to clipboard
Posted by: 臧圩人
Posted on: 2008-07-17 20:19

回复HenryShanley:

你好,谢谢你的建议!

根据你的建议我已经对文章的结尾做了修改,不过我没有改为JAVA只有值传递的观点。
我在客观地表述了两种观点之后,说出了自己的看法,我希望做到在保持自己立场的同时,不至于误导读者。

PS:我很欣赏你的严谨和负责的态度,呵呵。o(∩_∩)o...

15.Re:JAVA面试题解惑系列(五)——传了值还是传了引用? [Re: 臧圩人] Copy to clipboard
Posted by: JiafanZhou
Posted on: 2008-07-18 15:39

It reads much better than the original one I have to say. 3 points then.

16.Re:JAVA面试题解惑系列(五)——传了值还是传了引用? [Re: 臧圩人] Copy to clipboard
Posted by: newfish
Posted on: 2008-09-22 15:25

// 为方法参数重新赋值
public void change(ParamTest t) {
ParamTest tmp = new ParamTest();
tmp.num = 9;
t = tmp;
}

更改为
public void change(ParamTest t) {
t.num = 9;
}
一样可以重新对原始变量进行赋值,不是非要用运算吧.

17.Re:JAVA面试题解惑系列(五)——传了值还是传了引用? [Re: newfish] Copy to clipboard
Posted by: JiafanZhou
Posted on: 2008-09-25 16:39

newfish wrote:
// 为方法参数重新赋值
public void change(ParamTest t) {
ParamTest tmp = new ParamTest();
tmp.num = 9;
t = tmp;
}

更改为
public void change(ParamTest t) {
t.num = 9;
}
一样可以重新对原始变量进行赋值,不是非要用运算吧.

Correct, and it is a much better way, in terms of avoiding creating a new instance of ParamTest.


   Powered by Jute Powerful Forum® Version Jute 1.5.6 Ent
Copyright © 2002-2021 Cjsdn Team. All Righits Reserved. 闽ICP备05005120号-1
客服电话 18559299278    客服信箱 714923@qq.com    客服QQ 714923