百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Java 到底是值传递还是引用传递?(java值传递与引用类型传递)

cac55 2024-11-09 11:13 21 浏览 0 评论

作者:Hollis

关于这个问题,引发过很多广泛的讨论,看来很多程序员对于这个问题的理解都不尽相同,甚至很多人理解的是错误的。还有的人可能知道Java中的参数传递是值传递,但是说不出来为什么。

在开始深入讲解之前,有必要纠正一下大家以前的那些错误看法了。如果你有以下想法,那么你有必要好好阅读本文。

错误理解一:值传递和引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。
错误理解二:Java是引用传递。
错误理解三:传递的参数如果是普通类型,那就是值传递,如果是对象,那就是引用传递。

实参与形参

我们都知道,在Java中定义方法的时候是可以定义参数的。比如Java中的main方法,public static void main(String[] args),这里面的args就是参数。参数在程序语言中分为形式参数和实际参数。

形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。
实际参数:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。

简单举个例子:

public static void main(String[] args) {
    ParamTest pt = new ParamTest();
    pt.sout("Hollis");//实际参数为 Hollis
}

public void sout(String name) { //形式参数为 name
    System.out.println(name);
}

实际参数是调用有参方法的时候真正传递的内容,而形式参数是用于接收实参内容的参数。

值传递与引用传递

上面提到了,当我们调用一个有参函数的时候,会把实际参数传递给形式参数。但是,在程序语言中,这个传递过程中传递的两种情况,即值传递和引用传递。我们来看下程序语言中是如何定义和区分值传递和引用传递的。

值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

有了上面的概念,然后大家就可以写代码实践了,来看看Java中到底是值传递还是引用传递 ,于是,最简单的一段代码出来了:

public static void main(String[] args) {
    ParamTest pt = new ParamTest();

    int i = 10;
    pt.pass(10);
    System.out.println("print in main , i is " + i);
}

public void pass(int j) {
    j = 20;
    System.out.println("print in pass , j is " + j);
}

上面的代码中,我们在pass方法中修改了参数j的值,然后分别在pass方法和main方法中打印参数的值。输出结果如下:

print in pass , j is 20
print in main , i is 10

可见,pass方法内部对name的值的修改并没有改变实际参数i的值。那么,按照上面的定义,有人得到结论:Java的方法传递是值传递。

但是,很快就有人提出质疑了(哈哈,所以,不要轻易下结论咯。)。然后,他们会搬出以下代码:

public static void main(String[] args) {
    ParamTest pt = new ParamTest();

    User hollis = new User();
    hollis.setName("Hollis");
    hollis.setGender("Male");
    pt.pass(hollis);
    System.out.println("print in main , user is " + hollis);
}

public void pass(User user) {
    user.setName("hollischuang");
    System.out.println("print in pass , user is " + user);
}

同样是一个pass方法,同样是在pass方法内修改参数的值。输出结果如下:

print in pass , user is User{name='hollischuang', gender='Male'}
print in main , user is User{name='hollischuang', gender='Male'}

经过pass方法执行后,实参的值竟然被改变了,那按照上面的引用传递的定义,实际参数的值被改变了,这不就是引用传递了么。于是,根据上面的两段代码,有人得出一个新的结论:Java的方法中,在传递普通类型的时候是值传递,在传递对象类型的时候是引用传递。

但是,这种表述仍然是错误的。不信你看下面这个参数类型为对象的参数传递:

public static void main(String[] args) {
    ParamTest pt = new ParamTest();

    String name = "Hollis";
    pt.pass(name);
    System.out.println("print in main , name is " + name);
}

public void pass(String name) {
    name = "hollischuang";
    System.out.println("print in pass , name is " + name);
}

上面的代码输出结果为

print in pass , name is hollischuang
print in main , name is Hollis

这又作何解释呢?同样传递了一个对象,但是原始参数的值并没有被修改,难道传递对象又变成值传递了?

Java中的值传递

上面,我们举了三个例子,表现的结果却不一样,这也是导致很多初学者,甚至很多高级程序员对于Java的传递类型有困惑的原因。

其实,我想告诉大家的是,上面的概念没有错,只是代码的例子有问题。来,我再来给大家画一下概念中的重点,然后再举几个真正恰当的例子。

值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

那么,我来给大家总结一下,值传递和引用传递之前的区别的重点是什么。



我们上面看过的几个pass的例子中,都只关注了实际参数内容是否有改变。如传递的是User对象,我们试着改变他的name属性的值,然后检查是否有改变。其实,在实验方法上就错了,当然得到的结论也就有问题了。

为什么说实验方法错了呢?这里我们来举一个形象的例子。再来深入理解一下值传递和引用传递,然后你就知道为啥错了。

你有一把钥匙,当你的朋友想要去你家的时候,如果你直接把你的钥匙给他了,这就是引用传递。这种情况下,如果他对这把钥匙做了什么事情,比如他在钥匙上刻下了自己名字,那么这把钥匙还给你的时候,你自己的钥匙上也会多出他刻的名字。

你有一把钥匙,当你的朋友想要去你家的时候,你复刻了一把新钥匙给他,自己的还在自己手里,这就是值传递。这种情况下,他对这把钥匙做什么都不会影响你手里的这把钥匙。

但是,不管上面那种情况,你的朋友拿着你给他的钥匙,进到你的家里,把你家的电视砸了。那你说你会不会受到影响?而我们在pass方法中,改变user对象的name属性的值的时候,不就是在“砸电视”么。

还拿上面的一个例子来举例,我们真正的改变参数,看看会发生什么?

public static void main(String[] args) {
    ParamTest pt = new ParamTest();

    User hollis = new User();
    hollis.setName("Hollis");
    hollis.setGender("Male");
    pt.pass(hollis);
    System.out.println("print in main , user is " + hollis);
}

public void pass(User user) {
    user = new User();
    user.setName("hollischuang");
    user.setGender("Male");
    System.out.println("print in pass , user is " + user);
}

上面的代码中,我们在pass方法中,改变了user对象,输出结果如下:

print in pass , user is User{name='hollischuang', gender='Male'}
print in main , user is User{name='Hollis', gender='Male'}

我们来画一张图,看一下整个过程中发生了什么,然后我再告诉你,为啥Java中只有值传递。



稍微解释下这张图,当我们在main中创建一个User对象的时候,在堆中开辟一块内存,其中保存了name和gender等数据。然后hollis持有该内存的地址0x123456(图1)。当尝试调用pass方法,并且hollis作为实际参数传递给形式参数user的时候,会把这个地址0x123456交给user,这时,user也指向了这个地址(图2)。然后在pass方法内对参数进行修改的时候,即user = new User();,会重新开辟一块0X456789的内存,赋值给user。后面对user的任何修改都不会改变内存0X123456的内容(图3)。

上面这种传递是什么传递?肯定不是引用传递,如果是引用传递的话,在user=new User()的时候,实际参数的引用也应该改为指向0X456789,但是实际上并没有。

通过概念我们也能知道,这里是把实际参数的引用的地址复制了一份,传递给了形式参数。所以,上面的参数其实是值传递,把实参对象引用的地址当做值传递给了形式参数。

我们再来回顾下之前的那个“砸电视”的例子,看那个例子中的传递过程发生了什么。



同样的,在参数传递的过程中,实际参数的地址0X1213456被拷贝给了形参,只是,在这个方法中,并没有对形参本身进行修改,而是修改的形参持有的地址中存储的内容。

所以,值传递和引用传递的区别并不是传递的内容。而是实参到底有没有被复制一份给形参。在判断实参内容有没有受影响的时候,要看传的的是什么,如果你传递的是个地址,那么就看这个地址的变化会不会有影响,而不是看地址指向的对象的变化。就像钥匙和房子的关系。

那么,既然这样,为啥上面同样是传递对象,传递的String对象和User对象的表现结果不一样呢?我们在pass方法中使用name = "hollischuang";试着去更改name的值,阴差阳错的直接改变了name的引用的地址。因为这段代码,会new一个String,在把引用交给name,即等价于name = new String("hollischuang");。而原来的那个”Hollis”字符串还是由实参持有着的,所以,并没有修改到实际参数的值。



所以说,Java中其实还是值传递的,只不过对于对象参数,值的内容是对象的引用。

总结

无论是值传递还是引用传递,其实都是一种求值策略(Evaluation strategy)。在求值策略中,还有一种叫做按共享传递(call by sharing)。其实Java中的参数传递严格意义上说应该是按共享传递。

按共享传递,是指在调用函数时,传递给函数的是实参的地址的拷贝(如果实参在栈中,则直接拷贝该值)。在函数内部对参数进行操作时,需要先拷贝的地址寻找到具体的值,再进行操作。如果该值在栈中,那么因为是直接拷贝的值,所以函数内部对参数进行操作不会对外部变量产生影响。如果原来拷贝的是原值在堆中的地址,那么需要先根据该地址找到堆中对应的位置,再进行操作。因为传递的是地址的拷贝所以函数内对值的操作对外部变量是可见的。

简单点说,Java中的传递,是值传递,而这个值,实际上是对象的引用。

而按共享传递其实只是按值传递的一个特例罢了。所以我们可以说Java的传递是按共享传递,或者说Java中的传递是值传递。

来源:知乎 链接:https://www.zhihu.com/question/31203609/answer/576030121

相关推荐

如何将PDF转换成TXT文档?介绍9种方法,轻松就能学会!

PDF和TXT作为两种较为常见的文档格式,各自以其独特的优势占据了重要地位,甚至在某些特定场景下,将PDF转为TXT会显得尤为重要。一、为什么选择将PDF转为TXT?提升阅读效率:TXT格式的纯文本...

怎么给电子文档加密?推荐用这四个方法,步骤简单,轻松学会!

最近朋友小李跟我吐槽,他熬夜整理的客户资料被同事误发到公司大群,差点酿成大祸。怎么给电子文档加密?其实,给电子文档加密就像给家门上锁一样简单。今天就手把手教你4种超实用的加密方法,小白也能3分钟搞定!...

怎么把Word文档转换成PDF?这3种方法很实用!

怎么把Word文档转换成PDF?PDF格式文件稳定,是传输文件首选格式,将Word文档转换成PDF文件格式是一种常见的需求,下面就来教大家三种方法,Word转PDF文件如此简单!01AdobeA...

"解决Word文档只读权限问题:四种有效方法帮您编辑文件"

你是否遇到过想编辑一个Word文档,却被告知你没有权限修改任何内容?这意味着文件已被设定为“只读”状态,也就是说你只能读取文件,却无权做出任何修改。发生此类状况的原因有很多,我们首先会分析具体的情况,...

如何把doc文档转换为word文档?批量转换的四个方法

在日常生活和工作中,我们经常会遇到需要将doc文档转换为word文档的情况。无论是出于兼容性的考虑,还是为了使用某些特定的功能,掌握这一技能都显得尤为重要。本文将详细介绍几种常见且高效的方法,帮助大家...

四种简易方法:快速将常见文档转换为PDF格式

由于PDF文件具有良好的视觉阅读性和通用性,因此在日常工作和学习中得到了广泛的应用。然而,与一些常见的文档相比,PDF文件的排版更加稳定,不易被修改,且在不同设备上查看时文档格式不容易出错。因此,在许...

Deepseek文档转换方法(deepseek投喂本地文档的方法)

Deepseek的火热程度大家都有目共睹,作为一种新的AI生产力工具,已在各个领域广泛应用。然而对大部分从没接触过此类智语言模型的新手来说,结合Deepseek进行生活、学习以及工作,还是有不小的挑战...

只要多加这样一句提示词,就能让DeepSeek帮你生成下载各种文档!

你提到的这个提示词技巧确实能极大提升效率!不过需要特别说明:DeepSeek作为AI助手不提供任何文档下载功能,但可以通过以下合法方式帮你更高效地获取知识:利用DeepSeek生成并下载文档,关键...

如何免费下载文档?原创力文档来啦!

在如今这个信息共享的时代,有许多渠道和方式可以让我们免费获取到所需的文档,只是很多人没有去深入探索和了解。免费下载文档可太实用了,既能给咱们省不少钱,还能让咱们自由自在的获取知识和信息。接下来我就把自...

用deepseek提问题,结果可以直接下载的excel文档

步骤:1.打开deepseek,录入你的要求,如请你帮我做一份会计日常工作安排表,内容按年、按月、按周的时间轴来写。2.写上提示词:以表格的方式呈现,用html格式输出,需要能直接下载excel文档的...

AI大模型推理优化(附原文pdf文档和DeepSeek资料下载)

大模型推理引擎采用了多种定制化的优化技术,针对预填充和解码阶段进行优化。大多数引擎都使用了KVCaching避免解码阶段的重复计算,使用缓存的上下文,只计算最新一个token。引入连续批处理和...

网上下载的Word文档打不开?这样解决!

你是不是遇到过这种情况?下载的Word文件打不开,双击无响应?出现“文件已损坏”或“受保护的视图”提示?别担心!这篇文章适用于最新版Office365,教你4种方法快速修复W...

密封垫片的选择,PPT文档(可全套下载),建议机械人都看看

密封垫片选择难题,PPT详解,机械工程资料等你解锁!工业生产中,密封是至关重要的一环,而密封垫片,就是守护这道防线的无名英雄。可小小的垫片,选择起来却让人头秃!型号繁多,参数复杂,让人摸不着头脑。你...

教你怎么破解收费文档的技巧,很多文档复制都是要钱,别说下载了

现在很多网站或文档,复制都需要交会员费,或者就是让你付费,有没有什么其他的办法解决这一问题呢?办法其实是有的,只是细节问题很难处理了。那么今天就来提供三种本人在实际应用中用到的破解限制的功能。来看看具...

如何免费 下载360文库文档?(360文库付费下载)

想要免费下载360文库的文档,你可以试试下面这几种办法:一、用冰点下载器:冰点下载器这个工具不用登录,也不用积分,就能免费下载文库文档。你只要把要下载的文档链接粘贴到搜索框里,然后点下载就行。二、...

取消回复欢迎 发表评论: