”Java总是按值传递的“解惑

最近在看《java核心技术卷一》(以下简称卷一)中看到这么一句话“Java程序设计语言总是采用值传递。“[是在方法参数一节中阐述的] 。对此也是有点不能理解,于是各种搜索,参考了下面的两篇文章,得出的结论是:Java程序设计语言总是采用值传递的。

参考的文章:

[本文中所述的第一篇文章]http://zhuh-techno.blog.sohu.com/20412124.html  【java参数传递时到底是值传递还是引用传递】

[本文中所述的第二篇文章]http://www.ibm.com/developerworks/cn/java/passbyval/index.html【Java 应用程序中的按值传递语义】

http://blog.csdn.net/ruoshui222/article/details/4795610 【[转]C++中值传递、指针传递、引用传递的总结】

http://blog.csdn.net/jubincn/article/details/7221969 【[译]Java中方法调用参数传递的方式是传值,尽管传的是引用的值而不是对象的值。

(Does Java pass by reference or pass by value?)】

网络上相关的问题是:当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,

那么这里到底是值传递还是引用传递?【JAVA面试32问】

在第一篇文章中作者得出的结论是:可以说是值传递,也可以说是引用传递。并分别阐述了理由,

就值传递:得出这种结论的前提必须是“参数的值就是对该对象的引用,而不是对象的内容”,这句话可能有些费解,举个例子加以说明。

就引用传递:如果在参数传递时理解为“参数的值就是该对象的内容”,那么显然不是值传递,因为对象的内容已经改变了。

[注:文章的例子不是很好,在此没有列举。疑惑的是这两种概念,如果是引用传递这种说法,

为什么卷一的作者说“Java程序设计语言总是采用值传递“我想他也是知道这种说法的,

我们可以批评的接收一个新知识,但是我想我要搞清楚为什么会这么说,而不是像我们想象的那样]

第二篇文章的作者Peter Haggar 是位于北卡罗莱纳州的 Research Triangle Park 的 IBM 的高级软件工程师[原文]他的说法是Java是按值传递的。

首先,我们也是评判的去接收他的观点,为什么大家都说可以说是值传递,也可以说是引用传递的,你要说是按值传递的。

节选第二篇文章的 要点 的片段:

对象确实是按引用传递的;节选与这没有冲突。节选中说所有 参数按值 -- 另一个参数 -- 传递的下面的说法是正确的:在 Java 应用程序中永远不会传递对象,而只传递对象引用。因此是按引用传递对象。但重要的是要区分参数是如何传递的,这才是该节选的意图。Java 应用程序按引用传递对象这一事实并不意味着 Java 应用程序按引用传递参数。参数可以是对象引用,而 Java 应用程序是按值传递对象引用的。“

Java 应用程序中的变量可以为以下两种类型之一:引用类型或基本类型。当作为参数传递给一个方法时,处理这两种类型的方式是相同的。

两种类型都是按值传递的;没有一种按引用传递。这是一个重要特性,正如随后的代码示例所示的那样。
在继续讨论之前,定义
按值传递按引用传递这两个术语是重要的。

按值传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的一个副本。

因此,如果函数修改了该参数,仅改变副本,而原始值保持不变。

按引用传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的内存地址,而不是值的副本。

因此,如果函数修改了该参数,调用代码中的原始值也随之改变。


在 C++ 和 Java 应用程序中,当传递给函数的参数不是引用时,传递的都是该值的一个副本(按值传递)。区别在于引用。在 C++ 中当传递给函数的参数是引用时,您传递的就是这个引用,或者内存地址(按引用传递)。在 Java 应用程序中,当对象引用是传递给方法的一个参数时,您传递的是该引用的一个副本(按值传递),而不是引用本身。请注意,调用方法的对象引用和副本都指向同一个对象。这是一个重要区别。Java 应用程序在传递不同类型的参数时,其作法与 C++ 并无不同。Java 应用程序按值传递所有参数,这样就制作所有参数的副本,而不管它们的类型。

举一个对象变量传递的例子:

class Test
{
  public static void main(String args[])
  {
    int val;
    StringBuffer sb1, sb2;
    val = 10;
    sb1 = new StringBuffer("apples");
    sb2 = new StringBuffer("pears");
    System.out.println("val is " + val);
    System.out.println("sb1 is " + sb1);
    System.out.println("sb2 is " + sb2);
    System.out.println("");
    System.out.println("calling modify");
    //按值传递所有参数
    modify(val, sb1, sb2);//2
    System.out.println("returned from modify");
    System.out.println("");
    System.out.println("val is " + val);
    System.out.println("sb1 is " + sb1);
    System.out.println("sb2 is " + sb2);
  }
  public static void modify(int a, StringBuffer r1,
                            StringBuffer r2)
  {
      System.out.println("in modify...");
      a = 0;
      r1 = null;  //1
      r2.append(" taste good");//3
      System.out.println("a is " + a);
      System.out.println("r1 is " + r1);
      System.out.println("r2 is " + r2);
  }
}
输出结果:

val is 10
sb1 is apples
sb2 is pears
calling modify
in modify...
a is 0
r1 is null
r2 is pears taste good
returned from modify
val is 10
sb1 is apples
sb2 is pears taste good

正如预期的那样,整型的 val 没有改变(按值传递)。对象引用 sb1 也没有改变。如果 sb1是按引用传递的,正如许多人声称的那样,它将为 null。但是,因为 Java 编程语言按值传递所有参数,所以是将 sb1 的引用的一个副本传递给了 modify 方法。当 modify 方法在 //1 位置将 r1 设置为 null 时,它只是对 sb1 的引用的一个副本进行了该操作,而不是像 C++ 中那样对原始值进行操作。

另外请注意,第二个对象引用 sb2 打印出的是在 modify 方法中设置的新字符串。即使 modify 中的变量 r2 只是引用 sb2 的一个副本,但它们指向同一个对象。因此,对复制的引用所调用的方法更改的是同一个对象。

我简单的画了一下内存图


最后一幅图也就解释了为什么执行完方法后,sb1的值没变,而sb2的值变了。引用类型在方法参数中传递的其实是对象的引用,对象的引用作为一个值传递给方法时,方法首先是对这个引用产生一个拷贝,在方法中操作的都是这个拷贝,当然,这个原始引用和拷贝都指向的是同一个对象。//1位置的这条语句,意义就是将引用变量r1指向null,这个操作并没有对sb1这个原始引用产生影响。为什么//3这个位置的语句会改变sb2的值?我的理解是:r2和sb2指向的是同一个对象,他们本身是这个对象的引用,我们调用的append方法对这个对象的方法,所以sb2的值改变了,其实准确的说是对象引用变量sb2引用的变量sb2的值改变了[口语中的简称带来的危害啊,就是容易混淆概念,简化的不一定都是好事,比如汉字什么的,就有人说简化的”爱”啊什么的,呵呵]想起这样一个比喻,觉得比较合适:Windows中的文本文件1和一个快捷方式1,当快捷方式1被传递给一个方法,方法对快捷方式1做了什么见不得人的事,使它不指向了文本文件1,而是指向了文本文件2,然后使用快捷方式1对文本做了修改,你说文本文件1的内容会改变吗?显然是不会的;但是,你如果使用快捷方式1打开文本文件1,调用文本文件1的方法改变了文本内容,文本文1件的内容改变了吗?……我可以选择不回答吗?嘿嘿……

类似于第二篇文章的作者一样,我也认为按引用传递 这个概念是被程序员从C++中带到Java中的,这个先入为主的观念很难被改变。

如果是按引用传递,如果Java和C++的按引用传递类似,那么在执行//1位置的代码时,

sb1的值就应该被改变了,

可惜没有,所以这不是按引用传递。


总结:

要理解“Java程序设计语言总是采用值传递。“ 这句话 最重要的地方是: 对象的引用可以作为一个值,传递给方法,

方法产生对象的引用的一个拷贝。由于这样对值的定义比较抽象,所以比较不好理解,

不是传统的值(1234的那种)。对象就像基本类型那样,传递的是一个引用变量的拷贝,原始引用和拷贝指向的对象一样。

关键点:值传递会得到原始值的一个拷贝,这样的方式是不是是数据得到了更好的保护?

摘自《Java核心技术卷一》中总结的Java中,方法参数的使用情况:

一个方法不能修改一个基本数据类型的参数(即数值型和布尔型);+字符串类型

一个方法可以改变一个对象参数的状态;

一个方法不能实现让对象参数引用一个新的对象。




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值