前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Java的传值调用

Java的传值调用

作者头像
编程大道
发布于 2020-02-11 09:42:43
发布于 2020-02-11 09:42:43
3.6K00
代码可运行
举报
文章被收录于专栏:编程大道编程大道
运行总次数:0
代码可运行

Java的传值调用

(本文非引战或diss,只是说出自己的理解,欢迎摆正心态观看或探讨)

引子

之所以写这篇文章是因为前些天写了一篇《Java中真的只有值传递么?》探讨了网上关于Java只有值传递的说法,当时写这篇文章的缘由是因为之前看的文章讲解的Java只有值传递,讲的不是让我很明白,没有拿出比较专业的解释或定义,没有说服我。而我在《Java中真的只有值传递么?》这篇文章中又做了一些解读,发现自己也是没有抓住重点,这才有了今天这篇文章,对之前的这篇文章做一个补充。

从那篇文章后,我了解到Java的参数传递其实牵涉到了Java语言的设计中的参数传递方式,可能在语言设计之时就考虑了这个问题,所以在工作之余自己简单的研究了一下,最终也能根据自己的理解解释一下关于Java是值传递还是引用传递的说法。

Java 是引用传递还是值传递现在有以下这些说法:

1、值传递和引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。 2、传递的参数如果是普通类型,那就是值传递,如果是对象,那就是引用传递。 3、Java中只有值传递。

关于这个问题应该是分情况讨论的,存在即合理,或许在不同的认识下有不同的说法,也不能简单的就说是值传递还是引用传递。

对或错都是相对的。

回顾

在谈这个问题之前我们先了解下值传递和引用传递的概念及现象。可以简单的通过几个例子来讲解的,大概是这样的。

值传递

例子1:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args){
    TestJavaParamPass() tjpp = new TestJavaParamPass();
    int num = 10;
    tjpp.change(num);
    System.out.println("num in main():"+i);
}
public void change(int param){
    param = 20;
    System.out.println("param in change():"+param);
}

控制台输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
param in change():20
num in main():10

mian()方法中的int变量num传递给change()方法,change()方法接收到后将值改变为20。通过看控制台输出,main()方法中的num变量的值没有改变。

结论:实参没有被形参影响,基本类型是值传递。

引用传递

例子2:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args){
    TestJavaParamPass() tjpp = new TestJavaParamPass();
    User user = new User();
    user.setName("Jerry");
    tjpp.change(user);
    System.out.println("user in mian():"+user);
}
public void change(User param){
    param.setName("Tom");
    System.out.println("param in change():"+param);
}

控制台输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
param in change():User(name=Tom}
user in mian():User(name=Tom}

main()方法中的user变量传递给change()方法,change()方法改变了其name属性值。通过看控制台输出,main()方法中的user变量的name属性值发生改变。

结论:形参变了实参也变了,引用类型是引用传递。

特殊的值传递

例子3:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args){
    TestJavaParamPass() tjpp = new TestJavaParamPass();
    String name = "Jerry";
    tjpp.change(name);
    System.out.println("name in main():"+i);
}
public void change(String param){
    param = "Tom";
    System.out.println("param in change():"+param);
}

控制台输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
param in change():Tom
name in mian():Jerry

String也是引用类型的数据类型,为什么值没改变?

因为在change()方法里param = "Tom";相当于param = new String("Tom");就相当于param被重新赋值指向了另外一个对象。所以,其实String类型传的是引用,只不过被重新赋值指向了别的对象了,没有修改原对象。即,String本质上还是引用传递,表像上是值传递。

结论:基本类型是值传递,引用类型是引用传递,String是特殊的值传递。

看到这样的结论,没有去深究过,可能大部分程序员的认知都是这样的。

根据上面的例子我们先初步给值传递和引用传递下个定义,以及解释为什么大多数程序员都将String理解为是特殊的值传递。

概念提取

与其叫概念提取还不如叫结论总结呢。

  • 值传递:基本类型的变量在被传递给方法时,传递的是该变量的值(即复制自己的值传递给方法)。
  • 引用传递:引用类型的变量在被传递给方法时, 传递的是该变量的引用(即自己所指向的内存地址)。
  • 为什么说String是特殊的值传递:是因为String和基本类型从表象来说表现出来的结果是一样,大概是为了便于记忆这个结果才这样说的吧。但是要知道String也是传递的引用,只不过它的引用被重新赋值,指向了别的对象了,所以不会影响原值。所以String不能简单的说是值传递。

而仅仅根据上面的实验就给值传递,引用传递下这样的结论是不是太草率了?

解析

对于文章开始时提到的那些说法,前两种可以这样解释:

大概是因为int没有因为change方法而改变原值,所以就说它传过去的是自身的值,因而叫值传递;User对象经过change方法后,对象的数据变了,就认为是因为实参和形参指向的是同一片内存空间,内存空间的数据变了就都变了,传过去的是引用所以就说对象是引用传递。这样说的侧重点是传递的东西。

所以,如果从传递的东西的角度来看这两种说法也是没问题的呀。

至于Java只有值传递的说法,我查阅了一些资料结合网上的文章了解到了求值策略这个名词,这大概牵涉到了语言本身的设计。所以就从这些名词来探究Java的方法调用时参数传递的奥秘。

我们先来看看这些编程语言里关于参数传递函数调用有关的术语。

(以下术语来自Wiki )

求值策略(Evaluation strategy)

在计算机科学中,求值策略(英语:Evaluation strategy)是确定编程语言中表达式的求值的一组(通常确定性的)规则。重点典型的位于函数或算子上——求值策略定义何时和以何种次序求值给函数的实际参数,什么时候把它们代换入函数,和代换以何种形式发生。

求值策略:是一组求值规则,用来定义如何为函数的实际参数求值。它是用来规定程序语言在方法、函数或过程调用时的传参策略,是在程序语言设计时就应该考虑的问题。而下面的这几个调用方式都属于求值策略。

传值调用(Call by value)

“传值调用”求值是最常见的求值策略,C和Scheme这样差异巨大的语言都在使用。在传值调用中实际参数被求值,其值被绑定到函数中对应的变量上(通常是把值复制到新内存区域)。如果函数或过程能把值赋给它的形式参数,则被赋值的只是局部拷贝——就是说,在函数返回后调用者作用域里的曾传给函数的任何东西都不会变。 传值调用不是一个单一的求值策略,而是指一类函数的实参在被传给函数之前就被求值的求值策略。尽管很多使用传值调用的编程语言(如Common Lisp、Eiffel、Java)从左至右的求值函数的实际参数,某些语言(比如OCaml)从右至左的求值函数和它们的实际参数,而另一些语言(比如Scheme和C)未指定这种次序(尽管它们保证顺序一致性)。

传值调用:在传值调用中,实际参数被求值后传递给被调函数。也就是说传值调用是实参在被传给函数之前就被求值的一种求值策略。

在Java中的体现

那什么叫实参在被传给函数之前就被求值呢?求的是谁的值呢?这个值又是什么呢?是怎么求得呢?

带着这些疑问,我们来看下面的例子。

如下,在调用change()方法时实参为i,当程序执行到change(i)这一行时,i是实参,这时i就要被求值了,会求出i的值即4传给change()方法;change()的形参a拿到的是实参i的值,是一个拷贝副本。

伪代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void change(int a){//拿到求得的实参的值
      a = a/2;
}
int i = 4;
change(i);
System.out.print(i);

因为是值的副本,所以在函数内对形参操作不会影响实参,所以输出是4。

这里我们举的例子是基本类型int类型的。那对于引用类型呢?

同样需要对实参求值,这时得到的值是实参的地址值,形参拿到的是实参的地址值,这个地址值指向的是u1等号后面使用new关键字开辟出来的那片内存空间,所以此时u2也指向这片内存空间,所以打印出来u2将会和u1输出同样的内容。

伪代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void change(User u2){//拿到求得的实参的地址值
      System.out.print(u2);
      u2 = getNewUser();
      u2.setName("$%#@*")
      System.out.print(u2);
}
public static void main(){ 
      User u1 = new User();
      u1.setName("1234");
      change(u1);
      System.out.print(u1);
}

然后,我们模仿上面的change(int a)的方法里,对形参接收到的值进行改变。注意,是形参的值,对change(User u2)来说,形参u2接收到的值是地址值,我们咋改变它呢?我们可以让u2指向另一个内存空间,即通过getNewUser()方法获取一个新的User对象,用这种方式给u2一个新的地址值,这不就改变了吗。

此时我们看输出,发现经过change()方法实参u1打印信息没变,为什么?因为u1的地址值没变,且u2是获得新地址后(指向另一片内存),在新的这片内存里操作的,故而不会影响到之前的那片内存空间的数据。

这样基本类型和引用类型的实验方法是一样的,看到的效果也是一样的,即实参没有随形参的改变而改变。

总结

最后得出的结论:从语言设计的角度,Java的方法调用时参数的求值策略是传值调用(Call by value)的。

如果我们想表达引用类型传递的是引用,仅仅是想说传的是引用不是别的东西的话,我们可以说的明确点:引用类型传的是引用,和程序语言中的求值策略不沾边 。那你说的引用传递就和求值策略中的传引用调用没关系,只是想表达传的是引用的话也没人会说你错。由此来看文章开头提到的前2种说法是不是也有解释的余地?

存在即合理,不同的说法有不同的前提条件不同的解释方式。如果是从程序语言设计的求值策略角度来问Java是哪种求值策略的话,那可以肯定的说是传值调用(Call by value)。

(以下术语摘抄自Wiki。能力有限,对这样些专业名词还无法完美解读,仅供参考)

附录

传引用调用和传共享对象调用都是求值策略的一种。

传引用调用(Call by reference)

在“传引用调用”求值中,传递给函数的是它的实际参数的隐式引用而不是实参的拷贝。通常函数能够修改这些参数(比如赋值),而且改变对于调用者是可见的。因此传引用调用提供了一种调用者和函数交换数据的方法。传引用调用的语言中追踪函数调用的副作用比较难,易产生不易察觉的bug。

很多语言支持某种形式的传引用调用,但是很少有语言默认使用它。FORTRAN II 是一种早期的传引用调用语言。一些语言如C++、PHP、Visual Basic .NET、C#和REALbasic默认使用传值调用,但是提供一种传引用的特别语法。

在那些使用传值调用又不支持传引用调用的语言里,可以用引用(引用其他对象的对象),比如指针(表示其他对象的内存地址的对象)来模拟。C和ML就用了这种方法。这不是一种不同的求值策略(语言本身还是传值调用)。它有时被叫做“传地址调用”(call by address)。这可能让人不易理解。在C之类不安全的语言里会引发解引用空指针之类的错误。但ML的引用是类型安全和内存安全的。

类似的效果可由传共享对象调用(传递一个可变对象)实现。比如Python、Ruby。

例:C用指针模拟的传引用调用

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void modify(int p, int* q, int* r) {
 p = 27; // passed by value: only the local parameter is modified
 *q = 27; // passed by value or reference, check call site to determine which
 *r = 27; // passed by value or reference, check call site to determine which
}

int main() {
 int a = 1;
 int b = 1;
 int x = 1;
 int* c = &x;
 modify(a, &b, c); // a是传值调用, b通过创建指针实现引用传递,c是按值传递的指针
 //b and x are changed
 return 0;
}

传共享对象调用(Call by sharing)

此方式由Barbara Liskov命名[1],并被Python、Java(对象类型)、JavaScript、Scheme、OCaml等语言使用。

与传引用调用不同,对于调用者而言在被调用函数里修改参数是没有影响的。如果要达成传引用调用的效果就需要传一个共享对象,一旦被调用者修改了对象,调用者就可以看到变化(因为对象是共享的,没有拷贝)。比如这段Python代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
def f(l):
 l.append(1)
 l = [2]
m = []
f(m)
print(m)

会输出[1]而不是[2]。因为列表是可变的,append方法改变了m。而赋值局部变量l的行为对外面作用域没有影响(在这类语言中赋值是给变量绑定一个新对象,而不是改变对象)。

使用C/C++语言的程序员可能因不能用指针等使函数返回多个值而感到不便,但是像Python这样的语言提供了替代方案:函数能方便的返回多个值,比C++11的std::tie更加简单。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-12-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 BiggerBoy 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java中真的只有值传递么?
关于这个问题应该是存在争议的。根据测试出来的结果和我们自己的经验,以及口口相传或是上学时老师讲的,我们认为是第一种。但第二种说法的呼声也很高,渐渐地我们也认为第2中才是对的。那么下面我们就来分析一下这个问题。
编程大道
2020/02/11
1.2K0
java是值传递还是引用传递 知乎_按值调用和按引用调用
最近整理面试题,整理到值传递、引用传递,到网上搜了一圈,争议很大。带着一脸蒙圈,线上线下查了好多资料。最终有所收获,所以分享给大家,希望能对你有所帮助。 首先说下我的感受,这个题目出的很好,但是在 Java 中这个题目是有问题的(在下面我会解释)。并且,有很多结论是 Java 中只有 值传递。我认为这样说不够严谨。当然如果针对 Java 语言本身来讲,Java 中只有 值传递,没有引用传递,是正确的。但是如果针对 值传递,引用传递的定义来说,Java 中还是有引用传递的。下面来分析:
全栈程序员站长
2022/11/03
9840
【Java】方法参数传递机制分析:传值与传引用
Java 的参数传递机制基于 传值,即方法接收参数时,会将实际传递给它的值复制给形参。这意味着,形参的变化不会影响到原始实参。这种传值机制对于基本数据类型(如 int, float, boolean 等)尤为明显。
CSDN-Z
2025/02/21
1860
【Java】方法参数传递机制分析:传值与传引用
Java 引用传递和值传递
这个问题的关键在于 a,b,x,y 的地址指向; y = x 与 b = a 是不等价的!! 发生改变的是 y 指向的值变成了和 x 指向的相同, 此时 y = AB(因为append方法改变的x原有的值) 而此时 b 的指向并没有发生改变。
星尘的一个朋友
2020/11/25
1.2K0
图解Java 中的参数传递是传值还是传引用?
值传递:传递的是实参的副本(更准确的说是实参引用的副本,因为形参接受的是对象的引用)
九转成圣
2024/04/10
2250
图解Java 中的参数传递是传值还是传引用?
Java 基础概念·Java 只有值传递
当我们调用一个有参函数的时候,会把实际参数传递给形式参数。在程序语言中,这个传递过程中有两种情况,即值传递和引用传递。
数媒派
2022/12/01
4300
你们不要再吵了! Java只有值传递..
对于引用类型 str,赋值运算符只会改变引用中所保存的地址,虽然原来的地址被覆盖掉了,str指向了一个新的对象,但是原来的那个老对象没有发生变化,他还是老老实实待在原来的地方!!!
玖柒的小窝
2021/11/05
4070
你们不要再吵了! Java只有值传递..
Golang中函数传参存在引用传递吗?
官方文档已经明确说明:Go里边函数传参只有值传递一种方式,为了加强自己的理解,再来把每种传参方式进行一次梳理。
大愚
2018/09/13
2.3K0
Golang中函数传参存在引用传递吗?
难住了同事:Java 方法调用到底是传值还是传引用
byte、short、int、long、float、double、char、boolean 是 Java 中的八种基本类型。基本类型的内存分配在栈上完成,也就是 JVM 的虚拟机栈。也就是说,当你使用如下语句时:
古时的风筝
2020/03/19
1K0
JAVA基础知识之JAVA的引用类型
          在Java中,除了基本数据类型之外,其它类型都是引用数据类型,比如String类型和自己定义的class类、数组、接口都是引用类型。
程序员飞飞
2020/05/27
8210
女朋友问我:小松子,你知道Go语言参数传递是传值还是传引用吗?
形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。
Golang梦工厂
2022/07/08
3650
女朋友问我:小松子,你知道Go语言参数传递是传值还是传引用吗?
Java 02 - 值传递与引用传递
首先我们要知道, 值传递和引用传递是一种求值策略(Evaluation Stragtegy), 表示的是调用函数的时候, 对于参数传递方式的描述, 而不是对参数本身类型的描述. 值类型和引用类型是两种内存分配方式, 值类型是在调用栈上分配, 而引用类型是在堆上分配. 一个是描述的内存分配方式, 一个是描述参数求值策略, 二者并无依赖和约束关系.
Reck Zhang
2021/08/11
7830
Java细节:深入了解引用类型
引用类型,是指除了基本类型之外的所有类型。所有的类型在内存中都会分配一定的存储空间(形参在使用的时候也会分配存储空间,方法调用完成之后,这块存储空间自动消失), 基本类型只有一块存储空间(分配在stack中), 而引用类型有两块存储空间(一块在stack中,一块在heap中),在函数调用时Java是传值还是传引用,改变一个变量的值,会不会影响其他变量,这个估计很多人至今都很糊涂,下面用图形与代码来解释:
鲲志说
2025/04/07
890
Java细节:深入了解引用类型
为什么Java中只有值传递【详解】
这里,我们可以清晰地看出x的值在函数执行前后没有发生改变,函数对形参的任何操作,不影响实参;
訾博ZiBo
2025/01/06
810
为什么Java中只有值传递【详解】
为什么Java只有值传递
值传递: 调用函数时,将实参复制一份传给函数,函数中修改参数时不会影响实参 引用传递:调用函数时,将实参的地址传给函数,函数中修改参数会影响实参。 判断是值传递还是引用传递的标准,和传递参数的类型是没有关系的。
Qwe7
2022/04/01
4820
Java真的只有值传递?
值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
一觉睡到小时候
2019/07/02
7110
Java真的只有值传递?
为什么大家都说Java中只有值传递?
最近跟Java中的值传递和引用传递杠上了,一度怀疑人生。查了很多资料,加上自己的理解,终于搞清楚了,什么是值传递和引用传递。也搞明白了,为什么大家都说Java只有值传递,没有引用传递。原来,我一直以来的认知都是错误的。。。
烟雨星空
2020/06/16
1.6K0
【C语言】传值调用与传址调用:深度解析与实现
传值调用是指在函数调用过程中,向函数传递的是实参的值的副本,即将实参的值复制一份传递给函数的形参。因此,函数内部对形参的操作是不会影响实参本身的。
CSDN-Z
2024/11/26
2000
【C语言】传值调用与传址调用:深度解析与实现
解惑4:java是值传递还是引用传递
曾经纠结了很久java的参数传递方式是什么样的,后面粗略的了解了一鳞半爪以后有了大概的印象:“传参数就是值传递,传对象就是引用传递”,后面进一步查找了相关资料和文章以后,发现这么理解是不正确的。
全栈程序员站长
2022/09/23
6610
解惑4:java是值传递还是引用传递
指针变量的传值和传址
解析:int func(int* pRes)函数的形参是指针类型 int *pRes,在函数体中 new了一块内存并赋值 12,将内存地址赋值给指针 pRes。在main函数中,定义了指针pInt,调用func函数,把pInt作为参数传入func函数中。结果*pInt并不是 12。
C语言与CPP编程
2020/10/22
2.8K0
相关推荐
Java中真的只有值传递么?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验