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

priority
Last Edited
Jun 16, 2021 10:26 AM
date
Jan 29, 2018
Tags
type
Post
URL
slug
evaluation-strategy
Created
Jun 15, 2021 07:08 AM
status
Published
tags
Java
summary
很多刚入门的程序员,甚至我code了两年多,都没有完全理解调用函数时候的值传递与引用传递。最近遇到了这个问题,有所困惑,所以将看到的文章总结一下,以java为例,详解区分值传递与引用传递,传值与传引用。

内存中变量的存储方式

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

'='的作用

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

求值策略

目前主要有三种方式
| 求值策略                    | 求值时间            | 传值方式               |
| ---------------------------| ----------------- | ---------------------- |
| 值传递(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 - 2021