Java参数是传值仍是传引用

Java参数传值仍是传引用

     参数是按值而不是按引用传递的说明 Java 应用程序有且仅有的一种参数传递机制,即按值传递。写它是为了揭穿广泛存在的一种神话,即认为 Java 应用程序按引用传递参数,以免因依赖“按引用传递”这一行为而致使的常见编程错误。
      对此节选的某些反馈意见认为,我把这一问题搞糊涂了,或者将它彻底搞错了。许多不一样意个人读者用 C++ 语言做为例子。所以,在此栏目中我将使用 C++ 和 Java 应用程序进一步阐明一些事实。
      
    要点
      读完全部的评论之后,问题终于明白了,考试吧提示: 至少在一个主要问题上产生了混淆。由于对象是按引用传递的。对象确实是按引用传递的;节选与这没有冲突。节选中说全部参数都是按值 -- 另外一个参数 -- 传递的。下面的说法是正确的:在 Java 应用程序中永远不会传递对象,而只传递对象引用。所以是按引用传递对象。但重要的是要区分参数是如何传递的,这才是该节选的意图。Java 应用程序按引用传递对象这一事实并不意味着 Java 应用程序按引用传递参数。参数能够是对象引用,而 Java 应用程序是按值传递对象引用的。
      
    C++ 和 Java 应用程序中的参数传递
      Java 应用程序中的变量能够为如下两种类型之一:引用类型或基本类型。看成为参数传递给一个方法时,处理这两种类型的方式是相同的。两种类型都是按值传递的;没有一种按引用传递。这是一个重要特性,正如随后的代码示例所示的那样。
      在继续讨论以前,定义按值传递和按引用传递这两个术语是重要的。按值传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的一个副本。所以, 若是函数修改了该参数,仅改变副本,而原始值保持不变。按引用传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的内存地址,而不是值的副本。 所以,若是函数修改了该参数,调用代码中的原始值也随之改变。
      上面的这些是很重要的,请你们注意如下几点结论,这些都是我认为的上面的文章中的精华和最终的结论:
      一、对象是按引用传递的
      二、Java 应用程序有且仅有的一种参数传递机制,即按值传递
      三、按值传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的一个副本
      四、按引用传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的内存地址,而不是值的副本
      首先考试吧来看看第一点:对象是按引用传递的
      确实,这一点我想你们没有任何疑问,例如:
      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

    下一个问题: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规则》)
      一、对象是按引用传递的
      二、Java 应用程序有且仅有的一种参数传递机制,即按值传递
      三、按值传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的一个副本
      四、按引用传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的内存地址,而不是值的副本
      
    三句话总结一下:
      1.对象就是传引用
    c++

          2.原始类型就是传值程序员

          3.String类型由于没有提供自身修改的函数,每次操做都是新生成一个String对象,因此要特殊对待。能够认为是传值。编程

     

    ==========================================================================app

    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是传引用,那么传递的是引用的副本。)spa

      在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都是这样解释的:“不论是基本类型仍是对象类型,都是传值。”这种说法也不能算错,由于它们把引用副本也当作是一种“值”。可是笔者认为:传值和 传引用原本就是两个不一样的内容,不必把二者弄在一块儿,弄在一块儿反而更不易理解。

    相关文章
    相关标签/搜索