java对象引用出错_“Java有值传递和引用传递”为什么错了?

前言

初学Java的时候,老师在课堂上说“Java有值传递和引用传递”,但网上“Java只有值传递”的呼声很高。

本人在查找资料的过程中,在这两个说法之间反复横跳。经过本人的整理后,其实还真的是Java只有值传递。

什么是值传递?什么是引用传递?

首先,我们先明确一下值传递和引用传递的定义(来自维基百科)。

值传递

When a parameter is passed by value, the caller and callee have two independent variables with the same value. If the callee modifies the parameter variable, the effect is not visible to the caller.

拙译:当一个参数进行值传递的时候,调用者和被调用者有两个值相同的独立变量。如果被调用者修改了参数值,并不会影响调用者。

引用传递

When a parameter is passed by reference, the caller and the callee use the same variable for the parameter. If the callee modifies the parameter variable, the effect is visible to the caller’s variable.

拙译:当一个参数进行引用传递的时候,调用者和被调用者使用同一个变量。如果被调用者修改了参数值,调用者也会受到影响。

总结一下,值传递和引用传递最本质的区别在与,调用者和被调用者有没有使用同一个变量。

可能有人还是对这两个定义抱有疑问,没关系,我们用C++做个例子。

C++中的值传递和引用传递(不感兴趣可略过)

这里先说明下为什么用C++。因为C++的有指针概念,所以对于指针和引用是有做严格区分的,感兴趣的小伙伴可以看下这篇博客:C/C++中的值传递,引用传递,指针传递,指针引用传递。

基于需要,本文对C++的值传递和引用传递来进行简要说明。

C++的值传递

95e325e6f0f61a323656d39523ad49f2.png

可以看到,a变量(地址为0x22fe4c)在调用f()函数,进行值传递的变量p(地址为0x22fe20)已经是另一个变量了,而且在改变p的值后,地址为0x22fe4c的内容并没有改变,即a的值没有改变。所以值传递无法改变传递的变量的值。

引用传递

17665fecdce44091c5b97a16df5ff520.png

可以看到,a变量(地址为0x22fe4c)在调用f()函数,进行值传递的变量p(地址为0x22fe4c)还是同一个变量,而且在改变p的值后,地址为0x22fe4c的内容变为0xff,即a的值发生改变。所以引用传递可以改变传递的变量的值。

总结一下,在C++中的值传递和引用传递在大体上是跟值传递和引用传递的定义相符的。也就是说这个定义是可以在编程语言中适用的。

Java中的值传递和引用传递

说明一下,System.identityHashCode()的作用是用来判断两个对象是否是内存中同一个对象,跟用==判断内存地址是否一样的效果一样,有疑惑的朋友可以看下这篇博客:Java:Object.hashCode()和System.identityHashCode()的区别

值传递

fb518eff2ee554aa02e180e624f2aec1.png

以上可以得到跟值传递定义一样的结论,Java的值传递过程中,会复制传递的参数值到另一个变量,这两个变量之间互不影响,而且只有基本数据类型进行传递时是以值传递的方式。

引用传递

5a7168f580e409644e8fa9032f0cfa9b.png

从这个图也可以看出,Java引用传递过程中的两个数组a, b是指向同一个内存地址,这一个变量的改变会影响到另一个变量,而且只有除了String类型以外的其他对象类型在作为参数传递时,是使用引用传递的。

从这两个例子来看,Java既有值传递也有引用传递啊,所以“Java只有值传递”这个说法是错误的?非也。

String类型?

在查找资料的过程中,很多人的说法是“String类型也是值传递”,为什么呢?举个栗子:

public class Test {

public static void main(String[] args){

Test t = new Test();

String s = "oh";

t.test(s);

System.out.println("print in main , ans is " + s);

}

public void test(String s) {

s = "hello";

System.out.println("print in test , ans is " + s);

}

}

运行的结果是这样的:

print in test , ans is hello

print in main , ans is oh

我们可以看到实参s在传入方法test()后,形参s’改变了也不影响实参的值。为什么?(以下解释基于《深入理解Java虚拟机》的理解)

在字符串s传递过程中:

虚拟机在常量池划出一块内存,其地址为addr1,存值“oh”;

虚拟机在栈中分配s一块内存,内存中存的值为addr1;

虚拟机将s复制一份出来,即s‘,s和s’内存不同,但是它们的值都是addr1;

将s’传入方法体;

方法体在常量池中划分一块内存,其地址为addr2,存值"hello";

方法体将s’值改为addr2;

方法结束,main打印的是s,因为s存的值为addr1,所以打印出来的结果为addr1存的值:“oh”

所以String类型在调用过程中也是采用值传递。

“Java只有值传递”是错误的吗?

不是,我们拿“Java引用传递”的例子来解释。

public static void main(String[] args) {

int[] a = {1, 2, 3};

f(a);

...

}

public static void f(int[] b) {

b[0] = 100;

...

}

我们在得到结论的时候,是因为:在经过方法f()之后,a[0]的值从1改变为100。可这个过程严格上是引用传递吗?我们从虚拟机内部来观察传递过程:

虚拟机在堆上划分了一块用于存储数组a的内存,其内存地址为addr1。

虚拟机在栈中分配给a一个内存地址,这个地址存的是addr1。

虚拟机复制a,其别名为b,a和b的内存地址不同,但是他们的值都是addr1。

将b传入方法,方法改变了addr1的数组的值。

方法结束,f和main打印的都是addr1的内存值,都是同一个对象。

在这个过程中,a和b的内存地址不同,但是他们存值的内存地址后的对象是同一个。就是下图的这种关系:

11a7ef6999498d194486a8cd21baf26a.png

所以,从虚拟机的角度来看,实参a和形参b是两个独立变量,只是实参a把对象地址当做值传递给形参b。按照值传递的定义来看,a和b只是两个值相同的独立变量,Java是值传递。而a和b的值的值(这里不是写错)所指向的内容是同一个,所以我们前面在看到是“引用传递”的情况。

总结

严格来说,Java只有值传递,因为在实参传递的过程中,虚拟机复制了实参的值到形参,并且实参和形参指向的不是同一块内存。这个说法,是基于这种逻辑(a、b、c这三个变量是独立的,b为对象地址):

c72163e2ca6fbcdb79b748c69198eee4.png

这时,我们只要保证实参和形参是两个独立的个体,且值都是b就好。

而"Java有值传递和引用传递"这一说法的出现,是因为我们在刚学习Java的时候还不到了解虚拟机的水平,没有了解到,实参和形参是两个值相同的不同独立体,利用这种“美好的”误会来理解“引用传递”吧。

参考资料:

以及正文中提及的文章

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/291326.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

程序员生存之道-教你如何在丛林中捕获食物

文章目录 💥 序言🐣 躺🥝 从零到一还是从零到100?🍄 螺丝钉文化🍔 价值分析🍓 长期主义者?🥬 何为顺其自然?🌈 总结 💥 序言 嗨&#…

微软发文庆祝 .NET 诞生 20 周年纪念日!

技术编辑:MissD丨发自 思否编辑部公众号:SegmentFault刚刚过去的“情人节”里,.NET 团队为庆祝 .NET 社区诞生 20 周年而举办了一场盛大的活动。没错!.NET 于 2002 年 2 月 13 日与 Visual-Studio 一起推出,本月终于迎…

根据文件扩展名得到文件对应该类型Icon方法

2019独角兽企业重金招聘Python工程师标准>>> 根据文件扩展名得到文件对应该类型Icon方法 package com.fleety.util; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.…

Asp-Net-Core开发笔记:在docker部署时遇到一个小坑

哦吼之前刚说了尝试了使用docker来部署AspNetCore应用(Asp.Net Core部署:早知道,还是docker!以及一点碎碎念),结果这才刚上班就遇到问题了 …我这项目用的数据库是Oracle,之前直接运行没啥问题,…

lasso特征选择python_转:结合Scikit-learn介绍几种常用的特征选择方法-2

4.2 平均精确率减少 Mean decrease accuracy另一种常用的特征选择方法就是直接度量每个特征对模型精确率的影响。主要思路是打乱每个特征的特征值顺序,并且度量顺序变动对模型的精确率的影响。很明显,对于不重要的变量来说,打乱顺序对模型的精…

Mac Generating Pods project Abort trap: 6

为什么80%的码农都做不了架构师?>>> 为项目添加cocoapods如果产生此种错误时,主要有以下几点原因: 1,cocoapods版本过低: 打开终端在终端输入:pod --version,目前最新版本是1.2.0(2017年3月),如果发现版本过低,则可以在终端输入以下命令:gem install co…

svn 服务器搭建

2019独角兽企业重金招聘Python工程师标准>>> Svn搭建 1. Linux 搭建 YUM 服务器 [rootlocalhost conf]# yum install -y subversion 2.验证安装版本: [rootlocalhost conf]# svnserve –version 3.创建SVN 版本库 [rootlocalhost conf]# mkdir /v…

C# 使用 ValueTasks

C# 7 带有更灵活的 await 关键字;它现在可以等待任何提供 GetAwaiter 方法的对象。一种可用于等待的新类型是 ValueTask。与 Task 类相反,ValueTask 是一个结构。这具有性能优势,因为 ValueTask 在堆上没有对象。与异步方法调用相比&#xff…

Electron - 创建跨平台的桌面客户的应用程序

Electron 框架的前身是 Atom Shell,可以让你写使用 JavaScript,HTML 和 CSS 构建跨平台的桌面应用程序。它是基于io.js 和 Chromium 开源项目,并用于在 Atom 编辑器中。Electron 是开源的,由 GitHub 维护,有一个活跃的…

Cognos TM1_10.1.1服务端配置

场景:本文继Cognos TM1_10.1.1服务端安装 之后,简单的说一下本人对简单配置的拙见,确保服务端在安装过程一切正常,成功安装。 1:进入TM的Cognos Configuration 2:如下图,选中环境,这里可以看出…

java黄油刀_一篇文章玩转ButterKnife,让代码更简洁

前言话说,Android开发的兄弟们都知道,每次初始化控件,设置相应的事件,写的那点过程多而且恶心。我们先一块回顾下不堪的曾经~那些年,我们是这样初始化控件:// 每次的习惯上来写一个initView()方法tvContent…

nodejs的内存管理,垃圾回收机制

2019独角兽企业重金招聘Python工程师标准>>> 要点记录: 1、网页js、命令行工具,快进快出的,即时内存泄露,无内存管理必要! 2、服务器端nodejs和其他正规语言一样存在内存泄露。 3、nodejs基于谷歌v8js引擎&#xff…

Redis【第二篇】集群搭建

第一步:准备 1.安装包 ruby-2.4.0.tar.gz rubygems-2.6.10.tgz zlib-1.2.11.tar.gz redis-3.3.2.gem 2. 架构: 名称IP端口节点属性redisA192.168.6.1286379主节点redisB192.168.6.1289379从节点redisC192.168.6.1296379主节点redisD192.168.6.1299379从节…

(转)java中对集合对象list的几种循环访问总结

Java集合的Stack、Queue、Map的遍历在集合操作中,常常离不开对集合的遍历,对集合遍历一般来说一个foreach就搞定了,但是,对于Stack、Queue、Map类型的遍历,还是有一些讲究的。最近看了一些代码,在便利Map时…

NET框架下如何使用PaddleOCRSharp

打开VSIDE,新建Windows窗体应用(.NETFramework)类型的项目,选择一个.NET框架,如.NETFramework 4.0,右键点击项目,选择属性》生成,目标平台设置成X64.菜单》工具》选项,Nuget包管理器》程序包管理&#xff0…

Redis主从复制(Master-Slave Replication)

案例测试&#xff1a;1. Master新增网卡&#xff0c;修改server端配置IP : 192.168.40.128/24注释&#xff1a; bind&#xff0c;支持网络连接2. 新建虚机slave&#xff0c;配置网络&#xff0c;修改redis配置#slaveof <masterip> <masterport>slaveof 192.168.40.…

如何对一组 IP 地址 进行排序?

咨询区 Cracker我有一组如下IP地址。192.168.1.5 69.52.220.44 10.152.16.23 192.168.3.10 192.168.1.4 192.168.2.1我在寻找一个方法将他们排序成如下顺序。10.152.16.23 69.52.220.44 192.168.1.4 192.168.1.5 192.168.2.1回答区 Alex Aza对 ip 地址进行排序&#xff0c;大概…

如何为APK签名?

1.用来生成应用签名的文件①默认: debug.keystore > debug签名的应用程序不能在Android Market上架销售&#xff0c;它会强制你使用自己的签名。> 不同电脑使用此文件生成的签名不一样。那就意味着如果你换了机器进行apk版本升级&#xff0c;那么将会出现上面那种程序不能…

基于 Azure 的认知服务将文本合成语音

基于 Azure 的认知服务将文本合成语音Intro前几天发了一个 .NET 20 周年祝福视频&#xff0c;语音是通过 Azure 的认知服务合成的&#xff0c;下面就来介绍一下如何将使用 Azure 的认识服务实现将文本合成为语音Prepare你可以在 Azure Portal 上创建一个免费的语音服务&#xf…

sql查询结果集根据指定条件排序的方法

oracle认为 null 最大。 升序排列&#xff0c;默认情况下&#xff0c;null值排后面。 降序排序&#xff0c;默认情况下&#xff0c;null值排前面。 有几种办法改变这种情况&#xff1a; &#xff08;1&#xff09;用 nvl 函数或decode 函数 将null转换为一特定值 &#xff08;2…