为什么说 Java 中只有值传递?这个问题也颠覆了我学习/工作多年的认知。最近也是拜读了Hollis
的博客 为什么说 Java 中只有值传递 才有了新的认识,也欢迎大家去阅读原文。
Hollis
张洪亮,阿里巴巴技术专家,51CTO 专栏作家,CSDN 博客专家,掘金优秀作者,《程序员的三门课》联合作者,《Java 工程师成神之路》系列文章作者。
回答这个问题,先需要明确几个概念什么是值传递与引用传递与什么是求值策略。
什么是值传递与引用传递
值传递(pass by value)
是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
浓缩一下:会创建副本,函数中无法改变原始对象。引用传递(pass by reference)
是指在调用函数时将实际参数的地址直接传递到函 数中,那么在函数中对参数所进行的修改,将影响到实际参数。
浓缩一下:不创建副本,函数中可以改变原始对象。
什么是求值策略
在计算机科学中,求值策略(Evaluation strategies)是确定编程语言中表达式的求值的一组(通常确定性的)规则。求值策略定义何时和以何种顺序求值给函数的实际参数、什么时候把它们代换入函数、和代换以何种形式发生。
求值策略分为两大基本类,基于如何处理给函数的实际参数,分为严格的和非严格的。在“严格求值”中,函数调用过程中,给函数的实际参数总是在应用这个函数之前求值。多数现存编程语言对函数都使用严格求值。在这里我们只讨论严格求值。
在严格求值中有几个关键的求值策略是我们比较关心的,那就是传值调用(Call by value)、传引用调用(Call by reference)以及传共享对象调用(Call by sharing)。
传值调用(值传递)
在传值调用中,实际参数先被求值,然后其值通过复制,被传递给被调函数的形式参数。因为形式参数拿到的只是一个”局部拷贝”,所以如果在被调函数中改变了形式参数的值,并不会改变实际参数的值。
传引用调用(引用传递)
在传引用调用中,传递给函数的是它的实际参数的隐式引用而不是实参的拷贝。因为传递的是引用,所以,如果在被调函数中改变了形式参数的值,改变对于调用者来说是可见的。
传共享对象调用(共享对象传递)
传共享对象调用中,先获取到实际参数的地址,然后将其复制,并把该地址的拷贝传递给被调函数的形式参数。因为参数的地址都指向同一个对象,所以我们也称之为*“传共享对象”,所以,如果在被调函数中改变了形式参数的值,调用者是可以看到这种变化的。
共享对象传递的现象和引用传递也是一样的,那凭什么就说 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 para meters exist only within the scope of the method. When the method returns, the parameters are gone and any changes to them are lost.
即,原始参数通过值传递给方法。这意味着对参数值的任何更改都只存在于方法的范围内。当方法返回时,参数将消失,对它们的任何更改都将丢失。
《The Java™ Tutorials》中关于对象传递的描述:
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 对象的传递,传递的内容是对象的引用。