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

很多刚入门的程序员,甚至我code了两年多,都没有完全理解调用函数时候的值传递与引用传递。最近遇到了这个问题,有所困惑,所以将看到的文章总结一下,以java为例,详解区分值传递与引用传递,传值与传引用。

内存中变量的存储方式

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

两种存储方式如下图

‘=’的作用

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

求值策略

目前主要有三种方式

求值策略 求值时间 传值方式
值传递(pass by value) 调用前 值的结果(是原值的副本)
引用传递(pass by reference) 调用前 原值(原始对象,无副本)
名传递(pass by name) 调用后(用到才求值) 与值无关的一个名

所以,值传递,传递的是原值的一个副本,无法改变(mutate)原始对象,而引用传递,传递的是原来的值,可以改变对象。二者的区别是,是否会创建副本(注意,这里的改变指的是改变变量在内存中的值)

考虑如下函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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年前。

参考链接

为什么 Java 只有值传递,但 C# 既有值传递,又有引用传递,这种语言设计有哪些好处? - Hugo Gu的回答 - 知乎
Java 到底是值传递还是引用传递? - Intopass的回答 - 知乎

Song wechat
扫一扫,关注微信公众号,订阅我的博客
扫码领红包,支持走一波