关于java是值传递还是引用传递

 


一、概念

实际上对这两种传递方式,知乎上有个回答说得很好:

值传递和引用传递,属于函数调用时参数的求值策略(Evaluation Strategy),这是对调用函数时,求值传值的方式的描述,而非传递的内容的类型(内容指:是值类型还是引用类型,是值还是指针)。

值类型/引用类型,是用于区分两种内存分配方式,值类型在调用栈上分配,引用类型在堆上分配。

一个描述内存分配方式,一个描述参数求值策略,两者之间无任何依赖或约束关系

实际上意思就是,在函数调用时,不要通过传进去的参数类型去判断是值传递还是引用传递,而是要看这种语言具体的求值策略。

 

二、详解

在函数调用过程中,调用方提供实参,这些实参可以是常量:

Call(1);             // 常量
Call(x);             // 变量
Call(2 * x + 1);     // 常量与变量的组合
Call(GetNumber());   // 对其它函数的调用

但是所有这些实参的形式,都统称为表达式(Expression)

求值(Evaluation)即是指对这些表达式的简化并求解其值的过程。

求值策略(值传递和引用传递)的关注的点在于,这些表达式在调用函数的过程中,求值的时机值的形式的选取等问题。求值的时机,可以是在函数调用前,也可以是在函数调用后,由被调用者自己求值。这里所谓调用后求值,可以理解为Lazy Load或On Demand的一种求值方式。

除了值传递和引用传递,还有一些其它的求值策略,详见下表:

求值策略

求值时间

传值方式

值传递(Pass by value)

调用前

值的结果(是原值的副本)

引用传递(Pass by reference)

调用前

原值(原始对象,无副本)

名传递(Pass by name)

调用后(用到才求值)

与值无关的一个名

重点是值传递和引用传递。上面给出的传值方式的表述有些单薄,下表列出了一些二者在行为表象上的区别:

 

值传递

引用传递

根本区别

会创建副本(Copy)

不创建副本

结果

函数中无法改变原始对象

函数中可以改变原始对象

这里的改变不是指mutate, 而是change,指把一个变量指向另一个对象,而不是指仅仅改变属性或是成员什么的,所以说Java是Pass by value,原因是它调用时Copy,实参不能指向另一个对象,而不是因为被传递的东西本质上是个Value。

这些行为,与参数类型是值类型还是引用类型无关。对于值传递,无论是值类型还是引用类型,都会在调用栈上创建一个副本,不同是,对于值类型而言,这个副本就是整个原始值的复制而对于引用类型而言,由于引用类型的实例在堆中,在栈上只有它的一个引用(一般情况下是指针),其副本也只是这个引用的复制,而不是整个原始对象的复制。

综上所述,对于Java的函数调用方式最准确的描述是:参数藉由值传递方式,传递的值是个引用。(句中两个“值”不是一个意思,第一个值是evaluation result,第二个值是value content)。

 

三、例子

1. 值类型与引用类型

int num = 10;
String str = "hello";

1

num是基本类型,值就直接保存在变量中。而str是引用类型,变量中保存的只是实际对象的地址。一般称这种变量为"引用",引用指向实际对象,实际对象中保存着内容,比较特别的是,string对象是不可变对象

 

2. 函数调用

1) 基本类型

void foo(int value) {
    value = 100;
}
foo(num); // num 没有被改变

传进foo()里的只是num的一个副本。

2) 没有提供改变自身方法的引用类型

void foo(String text) {
    text = "world";
}
str=”hello”
foo(str); // str 也没有被改变

整个过程如下图所示:

11

3) 提供了改变自身方法的引用类型

StringBuilder sb = new StringBuilder("iphone");
void foo(StringBuilder builder) {
    builder.append("4");
}
foo(sb); // sb指向内容被改变了,变成了"iphone4",但本身还是指向同样的地址。

append前:

2

append后:

3

4) 提供了改变自身方法的引用类型,但是不使用,而是使用赋值运算符

StringBuilder sb = new StringBuilder("iphone");
void foo(StringBuilder builder) {
    builder = new StringBuilder("ipad");
}
foo(sb); // sb指向内容没有被改变,还是 "iphone"。

赋值前:

4

赋值后:

5

 

四、参考

1. 知乎关于Java 到底是值传递还是引用传递的回答之一

2. 知乎关于Java 到底是值传递还是引用传递的回答之二

(完)

posted @   大师兄啊哈  阅读(802)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示