Java求值策略:值传递与引用传递


内存中变量的存储方式

  • 基本类型 基本类型指int,double,short 等类型,新建变量直接在内存中开辟对应大小的内存,数据直接存储在变量所表示的内存中。
  • 引用类型 创建对象的时候,在内存中开辟一段区域存储对象,将该区域的地址赋值给变量。所以变量存储的是一个地址。
两种存储方式如下图
notion image

'='的作用

  • 基本类型 修改变量对应内存的值。
  • 引用类型 若=后的对象不存在,则开辟一段新的区域存储新的对象,并将变量存储的地址指向新的地址;若已存在,则直接将变量存储的地址指向新的地址。
notion image

求值策略

目前主要有三种方式
| 求值策略 | 求值时间 | 传值方式 | | ---------------------------| ----------------- | ---------------------- | | 值传递(pass by value) | 调用前 | 值的结果(是原值的副本) | | 引用传递(pass by reference) | 调用前 | 原值(原始对象,无副本) | | 名传递(pass by name) | 调用后(用到才求值) | 与值无关的一个名 |
求值策略
求值时间
传值方式
值传递(pass by value)
调用前
值的结果(是原值的副本)
引用传递(pass by reference)
调用前
原值(原始对象,无副本)
名传递(pass by name)
调用后(用到才求值)
与值无关的一个名
所以,值传递,传递的是原值的一个副本,无法改变(mutate)原始对象,而引用传递,传递的是原来的值,可以改变对象。二者的区别是,是否会创建副本(注意,这里的改变指的是改变变量在内存中的值)
考虑如下函数
public void changeObj(Employee e){ e=new Employee(); e.salary=10000; } public void changeValue(int a){ a=10; } public static void main(String[] args) { Employee emp=new Employee(); emp.salary=8000; changeObj(emp) int value=100; changeValue(value); }
首先声明: Java是值传递
  • 基本类型 上述代码中的changeValue函数,值传递,调用时会在内存中创建局部变量a,并且赋值100(作为value的副本),所以函数修改了a的值,但是value的值没有发生变化。
  • 引用类型 上述的changeObj函数,值传递,传递的是emp的值,emp的值实际上是一个地址,所以函数调用时,会创建一个局部变量e作为emp的副本,其值为emp的值,也是一个地址,指向内存中的emp对象存储的位置。所以代码只是将e的值指向了一个新的地址,不会修改emp的salary。
    • 如果注释掉e=new Employee(); 呢?
      此时,e指向的对象和emp指向的对象是一个对象。所以当修改e.salary的时候,内存中对象的salary值被改变,所以emp.salary也被修改了。

总结

综上所述,对于Java的函数调用方式最准确的描述是:参数藉由值传递方式,传递的值是个引用。(句中两个“值”不是一个意思,第一个值是evaluation result,第二个值是value content)由于这个描述太绕,而且在字面上与Java总是传引用的事实冲突。于是对于Java,Python、Ruby、JavaScript等语言使用的这种求值策略,起了一个更贴切名字,叫Call by sharing。这个名字诞生于40年前。

参考链接

  1. 为什么 Java 只有值传递,但 C# 既有值传递,又有引用传递,这种语言设计有哪些好处?
  1. Java 到底是值传递还是引用传递?
 

© Song 2015 - 2024