我要完全给你讲清楚,Java就是值传递,不接受争辩的那种!

前言

关于Java中方法间的参数传递究竟是怎样的、为何不少人说Java只有值传递等问题,一直困惑着不少人,甚至我在面试的时候问过不少有丰富经验的开发者,他们也很难解释的很清楚。html

我好久也写过一篇文章,我当时认为我把这件事说清楚了,可是,最近在整理这部分知识点的时候,我发现我当时理解的还不够透彻,因而我想着经过Google看看其余人怎么理解的,可是遗憾的是没有找到很好的资料能够说的很清楚。java

因而,我决定尝试着把这个话题总结一下,从新理解一下这个问题。程序员

辟谣时间

关于这个问题,在StackOverflow上也引起过普遍的讨论,看来不少程序员对于这个问题的理解都不尽相同,甚至不少人理解的是错误的。还有的人可能知道Java中的参数传递是值传递,可是说不出来为何。面试

在开始深刻讲解以前,有必要纠正一下你们之前的那些错误见解了。若是你有如下想法,那么你有必要好好阅读本文。编程

错误理解一:值传递和引用传递,区分的条件是传递的内容,若是是个值,就是值传递。若是是个引用,就是引用传递。
bash

错误理解二:Java是引用传递。
oracle

错误理解三:传递的参数若是是普通类型,那就是值传递,若是是对象,那就是引用传递。编程语言

实参与形参

咱们都知道,在Java中定义方法的时候是能够定义参数的。好比Java中的main方法,public static void main(String[] args),这里面的args就是参数。参数在程序语言中分为形式参数和实际参数。ide

形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。函数


实际参数:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。

简单举个例子:

public static void main(String[] args) {
   ParamTest pt = new ParamTest();   
   pt.sout("Hollis");//实际参数为 Hollis } 
   public void sout(String name) {
    //形式参数为 name  
     System.out.println(name); }复制代码

实际参数是调用有参方法的时候真正传递的内容,而形式参数是用于接收实参内容的参数。

求值策略

咱们说当进行方法调用的时候,须要把实际参数传递给形式参数,那么传递的过程当中到底传递的是什么东西呢?

这实际上是程序设计中求值策略(Evaluation strategies)的概念。

在计算机科学中,求值策略是肯定编程语言中表达式的求值的一组(一般肯定性的)规则。求值策略定义什么时候和以何种顺序求值给函数的实际参数、何时把它们代换入函数、和代换以何种形式发生。

求值策略分为两大基本类,基于如何处理给函数的实际参数,分为严格的和非严格的。

严格求值

在“严格求值”中,函数调用过程当中,给函数的实际参数老是在应用这个函数以前求值。多数现存编程语言对函数都使用严格求值。因此,咱们本文只关注严格求值。

在严格求值中有几个关键的求值策略是咱们比较关心的,那就是传值调用(Call by value)、传引用调用(Call by reference)以及传共享对象调用(Call by sharing)。

  • 传值调用(值传递)

    • 在传值调用中,实际参数先被求值,而后其值经过复制,被传递给被调函数的形式参数。由于形式参数拿到的只是一个"局部拷贝",因此若是在被调函数中改变了形式参数的值,并不会改变实际参数的值。

  • 传引用调用(应用传递)

    • 在传引用调用中,传递给函数的是它的实际参数的隐式引用而不是实参的拷贝。由于传递的是引用,因此,若是在被调函数中改变了形式参数的值,改变对于调用者来讲是可见的。

  • 传共享对象调用(共享对象传递)

    • 传共享对象调用中,先获取到实际参数的地址,而后将其复制,并把该地址的拷贝传递给被调函数的形式参数。由于参数的地址都指向同一个对象,因此咱们称也之为"传共享对象",因此,若是在被调函数中改变了形式参数的值,调用者是能够看到这种变化的。

不知道你们有没有发现,其实传共享对象调用和传值调用的过程几乎是同样的,都是进行"求值"、"拷贝"、"传递"。你品,你细品。

我要完全给你讲清楚,Java就是值传递,不接受争辩的那种!


可是,传共享对象调用和内传引用调用的结果又是同样的,都是在被调函数中若是改变参数的内容,那么这种改变也会对调用者有影响。你再品,你再细品。

那么,共享对象传递和值传递以及引用传递之间到底有什么关系呢?

对于这个问题,咱们应该关注过程,而不是结果,由于传共享对象调用的过程和传值调用的过程是同样的,并且都有一步关键的操做,那就是"复制",因此,一般咱们认为传共享对象调用是传值调用的特例

咱们先把传共享对象调用放在一边,咱们再来回顾下传值调用和传引用调用的主要区别:

传值调用是指在调用函数时将实际参数`复制`一份传递到函数中,传引用调用是指在调用函数时将实际参数的引用`直接`传递到函数中。

我要完全给你讲清楚,Java就是值传递,不接受争辩的那种!


因此,二者的最主要区别就是是直接传递的,仍是传递的是一个副本。

这里咱们来举一个形象的例子。再来深刻理解一下传值调用和传引用调用:

你有一把钥匙,当你的朋友想要去你家的时候,若是你直接把你的钥匙给他了,这就是引用传递。


这种状况下,若是他对这把钥匙作了什么事情,好比他在钥匙上刻下了本身名字,那么这把钥匙还给你的时候,你本身的钥匙上也会多出他刻的名字。


你有一把钥匙,当你的朋友想要去你家的时候,你复刻了一把新钥匙给他,本身的还在本身手里,这就是值传递。


这种状况下,他对这把钥匙作什么都不会影响你手里的这把钥匙。

Java的求值策略

前面咱们介绍过了传值调用、传引用调用以及传值调用的特例传共享对象调用,那么,Java中是采用的哪一种求值策略呢?

不少人说Java中的基本数据类型是值传递的,这个基本没有什么能够讨论的,广泛都是这样认为的。

可是,有不少人却误认为Java中的对象传递是引用传递。之因此会有这个误区,主要是由于Java中的变量和对象之间是有引用关系的。Java语言中是经过对象的引用来操纵对象的。因此,不少人会认为对象的传递是引用的传递。

并且不少人还能够举出如下的代码示例:

public static void main(String[] args) { 
      Test pt = new Test(); 
     User hollis = new User();       
     hollis.setName("Hollis");      
      hollis.setGender("Male");       
      pt.pass(hollis);       
      System.out.println("print in main , user is " + hollis);     }
       public void pass(User user) {      
        user.setName("hollischuang");       
        System.out.println("print in pass , user is " + user);    
         }复制代码

输出结果:

print in pass , user is User{name='hollischuang', gender='Male'} print in main , 
user is User{name='hollischuang', gender='Male'}复制代码

能够看到,对象类型在被传递到pass方法后,在方法内改变了其内容,最终调用方main方法中的对象也变了。

因此,不少人说,这和引用传递的现象是同样的,就是在方法内改变参数的值,会影响到调用方。

可是,其实这是走进了一个误区。

Java中的对象传递

不少人经过代码示例的现象说明Java对象是引用传递,那么咱们就从现象入手,先来反驳下这个观点。

咱们前面说过,不管是值传递,仍是引用传递,只不过是求值策略的一种,那求值策略还有不少,好比前面提到的共享对象传递的现象和引用传递也是同样的。那凭什么就说Java中的参数传递就必定是引用传递而不是共享对象传递呢?

那么,Java中的对象传递,究竟是哪一种形式呢?其实,还真的就是共享对象传递。

其实在 《The Java™ Tutorials》中,是有关于这部份内容的说明的。首先是关于基本类型描述以下:

Primitive arguments, such as an int or a double, are passed into methods by value. This means that any changes to the values of the parameters exist only within the scope of the method. When the method returns, the parameters are gone and any changes to them are lost.

即,原始参数经过值传递给方法。这意味着对参数值的任何更改都只存在于方法的范围内。当方法返回时,参数将消失,对它们的任何更改都将丢失。

关于对象传递的描述以下:

Reference data type parameters, such as objects, are also passed into methods by value. This means that when the method returns, the passed-in reference still references the same object as before. However, the values of the object’s fields can be changed in the method, if they have the proper access level.

也就是说,引用数据类型参数(如对象)也按值传递给方法。这意味着,当方法返回时,传入的引用仍然引用与之前相同的对象。可是,若是对象字段具备适当的访问级别,则能够在方法中更改这些字段的值。

这一点官方文档已经很明确的指出了,Java就是值传递,只不过是把对象的引用当作值传递给方法。你细品,这不就是共享对象传递么?

其实Java中使用的求值策略就是传共享对象调用,也就是说,Java会将对象的地址的拷贝传递给被调函数的形式参数。只不过"传共享对象调用"这个词并不经常使用,因此Java社区的人一般说"Java是传值调用",这么说也没错,由于传共享对象调用实际上是传值调用的一个特例。

值传递和共享对象传递的现象冲突吗?

看到这里不少人可能会有一个疑问,既然共享对象传递是值传递的一个特例,那么为何他们的现象是彻底不一样的呢?

难道值传递过程当中,若是在被调方法中改变了值,也有可能会对调用者有影响吗?那到底何时会影响何时不会影响呢?

实际上是不冲突的,之因此会有这种疑惑,是由于你们对于究竟是什么是"改变值"有误解。

咱们先回到上面的例子中来,看一下调用过程当中实际上发生了什么?

我要完全给你讲清楚,Java就是值传递,不接受争辩的那种!


在参数传递的过程当中,实际参数的地址0X1213456被拷贝给了形参。这个过程其实就是值传递,只不过传递的值得内容是对象的应用。

那为何咱们改了user中的属性的值,却对原来的user产生了影响呢?

其实,这个过程就好像是:你复制了一把你家里的钥匙给到你的朋友,他拿到钥匙之后,并无在这把钥匙上作任何改动,而是经过钥匙打开了你家里的房门,进到屋里,把你家的电视给砸了。

这个过程,对你手里的钥匙来讲,是没有影响的,可是你的钥匙对应的房子里面的内容倒是被人改动了。

也就是说,Java对象的传递,是经过复制的方式把引用关系传递了,若是咱们没有改引用关系,而是找到引用的地址,把里面的内容改了,是会对调用方有影响的,由于你们指向的是同一个共享对象。

那么,若是咱们改动一下pass方法的内容:

public void pass(User user) {    
 user = new User();    
  user.setName("hollischuang");  
    System.out.println("print in pass , user is " + user); }复制代码

上面的代码中,咱们在pass方法中,从新new了一个user对象,并改变了他的值,输出结果以下:

print in pass , user is User{name='hollischuang', gender='Male'} print in main , user is User{name='Hollis', gender='Male'}复制代码

再看一下整个过程当中发生了什么:

我要完全给你讲清楚,Java就是值传递,不接受争辩的那种!


这个过程,就好像你复制了一把钥匙给到你的朋友,你的朋友拿到你给他的钥匙以后,找个锁匠把他修改了一下,他手里的那把钥匙变成了开他家锁的钥匙。这时候,他打开本身家,就算是把房子点了,对你手里的钥匙,和你家的房子来讲都是没有任何影响的。

因此,Java中的对象传递,若是是修改引用,是不会对原来的对象有任何影响的,可是若是直接修改共享对象的属性的值,是会对原来的对象有影响的。

总结

咱们知道,编程语言中须要进行方法间的参数传递,这个传递的策略叫作求值策略。

在程序设计中,求值策略有不少种,比较常见的就是值传递和引用传递。还有一种值传递的特例——共享对象传递。

值传递和引用传递最大的区别是传递的过程当中有没有复制出一个副原本,若是是传递副本,那就是值传递,不然就是引用传递。

在Java中,实际上是经过值传递实现的参数传递,只不过对于Java对象的传递,传递的内容是对象的引用。

咱们能够总结说,Java中的求值策略是共享对象传递,这是彻底正确的。

可是,为了让你们都能理解你说的,咱们说Java中只有值传递,只不过传递的内容是对象的引用。这也是没毛病的。

可是,绝对不能认为Java中有引用传递。

OK,以上就是本文的所有内容,不知道本文是否帮助你解开了你心中一直以来的疑惑。欢迎留言说一下你的想法。

参考资料:

https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html

https://en.wikipedia.org/wiki/Evaluation_strategy

https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value

https://blog.penjee.com/passing-by-value-vs-by-reference-java-graphical/

转载公众号:Hollis,一个对Coding有着独特追求的人,现任阿里巴巴技术专家,我的技术博主,技术文章全网阅读量数千万,《程序员的三门课》联合做者。

相关文章
相关标签/搜索