`
newleague
  • 浏览: 1504384 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类

Java参数传值还是传引用

 
阅读更多

 参数是按值而不是按引用传递的说明 Java 应用程序有且仅有的一种参数传递机制,即按值传递。写它是为了揭穿普遍存在的一种神话,即认为 Java 应用程序按引用传递参数,以避免因依赖“按引用传递”这一行为而导致的常见编程错误。
  对此节选的某些反馈意见认为,我把这一问题搞糊涂了,或者将它完全搞错了。许多不同意我的读者用 C++ 语言作为例子。因此,在此栏目中我将使用 C++ 和 Java 应用程序进一步阐明一些事实。
  
要点
  读完所有的评论以后,问题终于明白了,考试吧提示: 至少在一个主要问题上产生了混淆。因为对象是按引用传递的。对象确实是按引用传递的;节选与这没有冲突。节选中说所有参数都是按值 -- 另一个参数 -- 传递的。下面的说法是正确的:在 Java 应用程序中永远不会传递对象,而只传递对象引用。因此是按引用传递对象。但重要的是要区分参数是如何传递的,这才是该节选的意图。Java 应用程序按引用传递对象这一事实并不意味着 Java 应用程序按引用传递参数。参数可以是对象引用,而 Java 应用程序是按值传递对象引用的。
  
C++ 和 Java 应用程序中的参数传递
  Java 应用程序中的变量可以为以下两种类型之一:引用类型或基本类型。当作为参数传递给一个方法时,处理这两种类型的方式是相同的。两种类型都是按值传递的;没有一种按引用传递。这是一个重要特性,正如随后的代码示例所示的那样。
  在继续讨论之前,定义按值传递和按引用传递这两个术语是重要的。按值传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的一个副本。因此,如果函数修改了该参数,仅改变副本,而原始值保持不变。按引用传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的内存地址,而不是值的副本。因此,如果函数修改了该参数,调用代码中的原始值也随之改变。
  上面的这些是很重要的,请大家注意以下几点结论,这些都是我认为的上面的文章中的精华和最终的结论:
  1、对象是按引用传递的
  2、Java 应用程序有且仅有的一种参数传递机制,即按值传递
  3、按值传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的一个副本
  4、按引用传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的内存地址,而不是值的副本
  首先考试吧来看看第一点:对象是按引用传递的
  确实,这一点我想大家没有任何疑问,例如:
  class Test01
  {
  public static void main(String[] args)
  {
  StringBuffer s= new StringBuffer("good");
  StringBuffer s2=s;
  s2.append(" afternoon.");
  System.out.println(s);
  }
  }
  对象s和s2指向的是内存中的同一个地址因此指向的也是同一个对象。
  如何解释“对象是按引用传递的”的呢?
  这里的意思是进行对象赋值操作是传递的是对象的引用,因此对象是按引用传递的,有问题吗?
  程序运行的输出是:
  good afternoon.
  这说明s2和s是同一个对象。
  这里有一点要澄清的是,这里的传对象其实也是传值,因为对象就是一个指针,这个赋值是指针之间的赋值,因此在java中就将它说成了传引用。(引用是什么?不就是地址吗?地址是什么,不过就是一个整数值)
  再看看下面的例子:
  class Test02
  {
  public static void main(String[] args)
  {
  int i=5;
  int i2=i;
  i2=6;
  System.out.println(i);
  }
  }
  程序的结果是什么?5!!!
  这说明什么,原始数据类型是按值传递的,这个按值传递也是指的是进行赋值时的行为。

下一个问题:Java 应用程序有且仅有的一种参数传递机制,即按值传递
  class Test03
  {
  public static void main(String[] args)
  {
  StringBuffer s= new StringBuffer("good");
  StringBuffer s2=new StringBuffer("bad");
  test(s,s2);
  System.out.println(s);//9
  System.out.println(s2);//10
  }
  static void test(StringBuffer s,StringBuffer s2) {
  System.out.println(s);//1
  System.out.println(s2);//2
  s2=s;//3
  s=new StringBuffer("new");//4
  System.out.println(s);//5
  System.out.println(s2);//6
  s.append("hah");//7
  s2.append("hah");//8
  }
  }
  程序的输出是:
  good
  bad
  new
  good
  goodhah
  bad
  考试吧提示: 为什么输出是这样的?
  这里需要强调的是“参数传递机制”,它是与赋值语句时的传递机制的不同。
  我们看到1,2处的输出与我们的预计是完全匹配的
  3将s2指向s,4将s指向一个新的对象
  因此5的输出打印的是新创建的对象的内容,而6打印的原来的s的内容
  7和8两个地方修改对象内容,但是9和10的输出为什么是那样的呢?
  Java 应用程序有且仅有的一种参数传递机制,即按值传递。
  至此,我想总结一下我对这个问题的最后的看法和我认为可以帮助大家理解的一种方法:
  我们可以将java中的对象理解为c/c++中的指针
  例如在c/c++中:
  int *p;
  print(p);//1
  *p=5;
  print(*p);//2
  1打印的结果是什么,一个16进制的地址,2打印的结果是什么?5,也就是指针指向的内容。
  即使在c/c++中,这个指针其实也是一个32位的整数,我们可以理解我一个long型的值。
  而在java中一个对象s是什么,同样也是一个指针,也是一个int型的整数(对于JVM而言),我们在直接使用(即s2=s这样的情况,但是对于System.out.print(s)这种情况例外,因为它实际上被晃猄ystem.out.print(s.toString()))对象时它是一个int的整数,这个可以同时解释赋值的传引用和传参数时的传值(在这两种情况下都是直接使用),而我们在s.XXX这样的情况下时s其实就是c/c++中的*s这样的使用了。这种在不同的使用情况下出现不同的结果是java为我们做的一种简化,但是对于c/c++程序员可能是一种误导。java中有很多中这种根据上下文进行自动识别和处理的情况,下面是一个有点极端的情况:
  class t
  {
  public static String t="t";
  public static void main(String[] args)
  {
  t t =new t();
  t.t();
  }
  static void t() {
  System.out.println(t);
  }
  }
  (关于根据上下文自动识别的内容,有兴趣的人以后可以看看我们翻译的《java规则》)
  1、对象是按引用传递的
  2、Java 应用程序有且仅有的一种参数传递机制,即按值传递
  3、按值传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的一个副本
  4、按引用传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的内存地址,而不是值的副本
  
三句话总结一下:
  1.对象就是传引用

      2.原始类型就是传值

      3.String类型因为没有提供自身修改的函数,每次操作都是新生成一个String对象,所以要特殊对待。可以认为是传值。

 

==========================================================================

public class Test03 {
 public static void stringUpd(String str) {
  str = str.replace("j", "l");
  System.out.println(str);
 } 
 public static void stringBufferUpd(StringBuffer bf) {
  bf.append("c");
  System.out.println(bf);
 } 
 public static void main(String[] args) {
  
  /**
   * 對於基本類型和字符串(特殊)是傳值
   *
   * 輸出lava,java
   */
  String s1 = new String("java");
  stringUpd(s1);
  System.out.println(s1);
  
  /**
   * 對於對象而言,傳的是引用,而引用指向的是同一個對象
   *
   * 輸出javac,javac
   */
  StringBuffer bb = new StringBuffer("java");
  stringBufferUpd(bb);
  System.out.println(bb);
 }
}

 

解析:就像光到底是波还是粒子的问题一样众说纷纭,对于Java参数是传值还是传引用的问题,也有很多错误的理解和认识。我们首先要搞清楚一点就是:不管Java参数的类型是什么,一律传递参数的副本。对此,thinking in Java一书给出的经典解释是When you’re passing primitives into a method, you get a distinct copy of the primitive. When you’re passing a reference into a method, you get a copy of the reference.(如果Java是传值,那么传递的是值的副本;如果Java是传引用,那么传递的是引用的副本。)

  在Java中,变量分为以下两类:

  ① 对于基本类型变量(int、long、double、float、byte、boolean、char),Java是传值的副本。(这里Java和C++相同)

  ② 对于一切对象型变量,Java都是传引用的副本。其实传引用副本的实质就是复制指向地址的指针,只不过Java不像C++中有显著的*和&符号。(这里Java和C++不同,在C++中,当参数是引用类型时,传递的是真实引用而不是引用副本)

  需要注意的是:String类型也是对象型变量,所以它必然是传引用副本。不要因为String在Java里面非常易于使用,而且不需要new,就被蒙蔽而把String当做基本变量类型。只不过String是一个非可变类,使得其传值还是传引用显得没什么区别。

  对基本类型而言,传值就是把自己复制一份传递,即使自己的副本变了,自己也不变。而对于对象类型而言,它传的引用副本(类似于C++中的指针)指向自己的地址,而不是自己实际值的副本。为什么要这么做呢?因为对象类型是放在堆里的,一方面,速度相对于基本类型比较慢,另一方面,对象类型本身比较大,如果采用重新复制对象值的办法,浪费内存且速度又慢。就像你要张三(张三相当于函数)打开仓库并检查库里面的货物(仓库相当于地址),有必要新建一座仓库(并放入相同货物)给张三么? 没有必要,你只需要把钥匙(引用)复制一把寄给张三就可以了,张三会拿备用钥匙(引用副本,但是有时效性,函数结束,钥匙销毁)打开仓库。

  在这里提一下,很多经典书籍包括thinking in Java都是这样解释的:“不管是基本类型还是对象类型,都是传值。”这种说法也不能算错,因为它们把引用副本也当做是一种“值”。但是笔者认为:传值和传引用本来就是两个不同的内容,没必要把两者弄在一起,弄在一起反而更不易理解。

分享到:
评论
1 楼 一剑寒天 2012-09-19  

相关推荐

    java中传值还是传引用的的认识

    "java中传值还是传引用的认识" Java 中的参数传递是值传递还是引用传递?这是一个经常引发讨论的问题。在 Java 中,参数传递是按值传递的,也就是说,传递给方法的参数是一个副本,而不是原始值本身。 当一个对象...

    Java是传值还是传址引用

    ### Java是传值还是传址引用 #### 一、简单类型是按值传递的 Java在处理简单数据类型(如int、boolean等)时采用的是按值传递的方式。这意味着当你将一个简单类型的值作为参数传递给一个方法时,实际上传递的是这...

    java中传值与传引用

    在Java编程语言中,函数调用时的参数传递方式有两种:传值(Passing by Value)和传引用(Passing by Reference)。虽然Java官方文档中并未明确指出有传引用这一概念,但在实际操作中,Java的行为类似于传引用,尤其...

    java的传值与传值后的改变

    理解Java中的传值与传引用对于编写正确且预期的行为代码至关重要。在编写函数时,应清楚地知道参数如何被传递,以及这将如何影响函数的可读性和行为。在实际编程中,合理运用这些知识能帮助避免许多常见的错误和困惑...

    23.Java对象作为参数传递是传值还是传引用1

    总结起来,Java中对象作为参数传递时,传递的是对象引用的副本,而不是对象本身。这意味着函数可以修改对象的状态,因为它持有相同的引用,但不能改变对象引用指向另一个不同的对象。这种行为与基本数据类型不同,...

    引用类型传值方法

    引用类型的传值行为体现在函数调用和方法参数传递上。当我们把一个引用类型变量作为参数传递给函数时,并不会创建该对象的新副本,而是复制了这个引用。这意味着函数内部对对象的任何修改都会影响到原始对象,因为...

    Java传值还是引用

    根据标题和描述,我们将深入探讨Java中传值与引用的区别,以及它们在实际编程中的应用。 首先,Java是一种“总是按值传递”的语言。这意味着无论是基本类型还是引用类型,当作为参数传递时,都会有一个副本被创建并...

    Java中的传值与传引用实现过程解析

    Java中的传值与传引用是Java编程语言中的一种基础概念,它们是Java函数中参数传递的两种方式。 Java中的传值是指函数参数的值被复制到函数内部,在函数内部对参数的修改不会影响原来的参数值。 Java中的传引用是指...

    浅析Java方法传值和传引用问题

    在Java编程语言中,方法参数传递机制涉及到两个主要概念:传值和传引用。了解这两个概念对于编写高效、可靠的代码至关重要。本篇文章将深入探讨Java中的方法传值和传引用问题。 首先,让我们理解什么是传值。在Java...

    浅谈Java中方法的参数传值.pdf

    Java方法参数传值是Java编程中的基础知识点,涉及变量、数据类型和方法调用的机制。在Java中,数据类型分为基本数据类型和引用数据类型。基本数据类型包括布尔型(boolean)、字节型(byte)、短整型(short)、整型...

    Java参数传递PPT

    Java参数传递的规则是:**Java只使用值传递,但这种值传递对于对象参数表现为类似引用传递的行为。** 在值传递中,函数或方法接收的是原始数据类型(如int、double、boolean)参数的副本。例如,如果有一个方法...

    java及C++中传值传递、引用传递和指针方式的理解.docx

    ### Java及C++中传值传递、引用传递和指针方式的理解 在程序设计语言中,函数调用时参数的传递方式对理解程序的行为至关重要。本文将深入探讨Java与C++这两种广泛使用的编程语言中参数传递的方式,包括值传递、引用...

    Java中参数传值的代码清单.pdf

    Java只支持两种类型的参数传递:基本数据类型(如int、double等)的传值和引用类型(如类对象)的传值。以下是对给定代码清单的详细解释: 1. **基本数据类型传值**: 在Java中,基本数据类型的参数传递是按值传递...

    Java到底是传引用还是传值Java开发Java经验技巧共

    Java编程语言在处理参数传递时遵循一种特殊的方式,它既不是纯粹的按值传递,也不是纯粹的按引用传递。理解这一点对于深入学习Java至关重要。在Java中,基本数据类型(如int、float、char等)是按值传递的,而对象则...

    JSP中java代码与js之间的传值

    - **通过JSP表达式语言(EL)**:使用EL表达式,如 `${javaVariable}`,可以直接在JavaScript代码中引用JavaBean或作用域内的变量。 2. **JavaScript到Java的传值**: - **表单提交**:通过JavaScript修改表单字段的...

    分析java的传值问题

    通过以上分析可以看出,Java中对于基本类型和引用类型的参数传递方式存在明显的区别: - 对于基本类型,传递的是值的副本,方法内部对参数的修改不会影响到原始变量。 - 对于引用类型,传递的是引用的副本,方法内部...

    Java语言的方法参数浅析

    通过对典型程序的研究与分析可以看出JAVA语言的参数传递总是传值调用的,但是对于基本类型的参数和对象类型的参数来说,参数传递的情况不完全相同.Java语言不能直接使用传引用调用,但是可以通过数组的方式模拟传引用...

    浅谈Java中方法的参数传值.zip

    Java中,方法参数的传递主要有两种方式:值传递(Pass by Value)和引用传递(Pass by Reference)。不过,Java并没有真正的引用传递,而是通过对象引用来模拟引用传递的效果。下面我们详细讨论这两种方式。 2. 值...

Global site tag (gtag.js) - Google Analytics