Java 到底是值传递还是引用传递?

2023-11-14

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

  • 错误理解一:值传递和引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。
  • 错误理解二: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中的传递是值传递。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java 到底是值传递还是引用传递? 的相关文章

  • windows driver双机调试环境搭建,用windbg或者debug view查看内核调试输出

    本地环境 本地开发环境 win 10 visual studio installer 2022版 visual studio 2022 VMware player 目的 本地环境作为开发环境 VM作为测试和调试环境 用debug view查
  • PCB天线设计

    PCB天线概述 什么是PCB天线 顾名思义 就是在PCB上印制了一根走线 可以将其画成直线走线 反转的F形走线 蛇形或圆形走线等 长度为四分之一波长就基本可以形成天线 将电信号辐射出去或接收信号 设计指标 在上一期文章 深入解读无线通信中的
  • 二叉树建立

    结束二叉树输入 如何结束创建二叉树的输入那 把二叉树补全 前序 输入 AB C 中序 B A C 后序 B CA 输出结果如下 代码如下 include
  • 前端性能优化:7.页面渲染优化

    本文将主要关注浏览器获取到资源后 进行渲染部分的相关优化内容 7 1 页面渲染性能 页面渲染阶段对性能体验的应i昂与资源加载阶段同样重要 而对于设计高交互频次的应用来说可能更加重要 本节将整个渲染过程划分为五个串行阶段进行概述 优化渲染的实
  • 大众点评校招助理产品经理-笔试题分析(下)

    针对大众点评网的任何一个产品的一个需要改进的点 信息 数据或功能体验都行 给出你的分析和改进规划 希望尽可能的全面 深入 详细 1 改进产品版本与使用机型 使用版本 大众点评Android客户端V5 2版本 机型 HTC Desire HD
  • memset in C++ and C

    definition memset是计算机中C C 语言函数 将s所指向的某一块内存中的前n个 字节的内容全部设置为ch指定的ASCII值 第一个值为指定的内存地址 块的大小由第三个参数指定 这个函数通常为新申请的内存做初始化工作 其返回值
  • Failed to execute goal org.apache.maven.plugins:maven-checkstyle-plugin

    idea编译项目时 出现如下报错 ERROR Failed to execute goal org apache maven plugins maven checkstyle plugin 3 0 0 check validate on p
  • k8s学习笔记(二):k8s的组件介绍

    文章目录 k8s组件介绍 架构图 角色 控制平面组件 Control Plane Components kube apiserver 端口 容器 etcd 端口 容器 kube scheduler 容器 端口 kube controller
  • Postman脚本——断言测试

    pm对象提供了测试相关功能 pm test testName String specFunction Function Function 测试函数 pm expect assertion Function Assertion 允许在响应数据
  • [552]python实现聚类算法(6种算法)

    1 Mean shift 1 概述 Mean shift 即 均值迁移 的基本思想 在数据集中选定一个点 然后以这个点为圆心 r为半径 画一个圆 二维下是圆 求出这个点到所有点的向量的平均值 而圆心与向量均值的和为新的圆心 然后迭代此过程
  • AI炼丹技巧

    目录 样本不平衡 1 什么是样本不平衡问题 2 样本不平衡会对我们模型的训练带来哪些影响呢 3 在机器学习中解决样本不平衡问题有哪些通用的解决策略 4 在深度学习中 有哪些解决样本不平衡的方法 5 从模型评价指标的角度 重新审视训练好的模型
  • 计算机中丢失msvcp140.dll怎么解决?分享三个解决方法

    当我们在运行某些应用程序时 可能会遇到 缺少msvcp140 dll 或 找不到msvcp140 dll 的错误提示 这意味着我们的系统缺少这个重要的动态链接库文件 msvcp140 dll丢失会造成很多软件无法正常启动运行 下面小编就分享
  • 全网最全!Python爬虫requests库教程(附案例)

    1 requests 库简介 Requests 是一个为人类设计的简单而优雅的 HTTP 库 requests 库是一个原生的 HTTP 库 比 urllib3 库更为容易使用 requests 库发送原生的 HTTP 1 1 请求 无需手
  • 【MySQL高级篇笔记-性能分析工具的使用 (中) 】

    此笔记为尚硅谷MySQL高级篇部分内容 目录 一 数据库服务器的优化步骤 二 查看系统性能参数 三 统计SQL的查询成本 last query cost 四 定位执行慢的 SQL 慢查询日志 1 开启慢查询日志参数 2 查看慢查询数目 3
  • 常见的排序算法及其复杂度分析

    1 常见算法分类 十种常见排序算法一般分为以下几种 非线性时间比较类排序 交换类排序 快速排序和冒泡排序 插入类排序 简单插入排序和希尔排序 选择类排序 简单选择排序和堆排序 归并排序 二路归并排序和多路归并排序 线性时间非比较类排序 计数
  • 黑盒测试与白盒测试的区別

    黑盒测试与白盒测试是软件测试中两种不同的测试方法 它们的主要区别在于测试者对被测试软件的了解程度 下面 我们将详细介绍这两种测试方法的特点和适用场景 一 黑盒测试 黑盒测试又称为功能测试 是针对被测试软件的功能进行测试的一种测试方法 测试者
  • linux 进程几种状态,linux进程状态

    linux系统最常用的命令莫过于ps 经常要用其查看linux的进程和线程情况 此文我们着重来看进程 进程又分为以下几种状态 1 运行 正在运行或在运行队列中等待 2 中断 休眠中 受阻 在等待某个条件的形成或接受到信号 3 不可中断 收到
  • Kotlin Coroutines Flow 系列(一) Flow 基本使用

    一 Kotlin Flow 介绍 Flow 库是在 Kotlin Coroutines 1 3 2 发布之后新增的库 官方文档给予了一句话简单的介绍 Flow cold asynchronous stream with flow build
  • C# socket异步通信服务器和客户端

    本文章向大家介绍C socket异步通信服务器和客户端 主要包括C socket异步通信服务器和客户端使用实例 应用技巧 基本知识点总结和需要注意事项 具有一定的参考价值 需要的朋友可以参考一下 服务器代码 只要客户端连接进来就会接收到Se
  • Qt之QTableView 保持滚动条自动滚动到底部

    在使用QTableView添加数据时 当数据量超出显示范围时会自动的打开滚动条 如果设置了在需要时打开滚动条属性 而默认的是滚动条一直是保持在最顶部 但是有些时候我们添加数据时是需要查看添加的最新结果的 所以一般都会把滚动条滚动到最新数据那

随机推荐

  • 【scrapy】scrapy爬取数据指南

    scrapy是爬虫界常用的基于Python爬虫框架 但是网上找了很多这类的文章 发现有多处错误 故为了让刚想尝试爬虫的蜘蛛们少走点坑 故把最新的方法奉上 在此之前 请先更新你的pip版本 并安装scrapy pymysql pip inst
  • kernelbase.dll崩溃的处理_kernelbase.dll故障教程

    kernelbase dll是存放在windows系统下的一个非常重要的dll文件 DLL英文全称Dynamic Link Library 中文意思是 动态链接库 DLL是一个作为共享函数库的可执行文件 它使进程可以调用不属于本身可执行代码
  • java 代理模式

    1 静态代理 1 1 代码实现 接口 interface Hello String sayHello String str 实现 class HelloImp implements Hello Override public String
  • 对象常用的方法

    思维导图 对象中常用的方法 Object prototype 1 hasOwnProperty 方法会返回一个布尔值 指示对象自身属性中是否具有指定的属性 也就是 是否有指定的键 检测是否为私有属性 即使属性的值是 null 或 undef
  • (五)redis常用命令之list

    概述 列表类型用来存储多个有序的字符串 列表中的每个字符串就是一个元素 一个列表最多可以存储2 32 1个元素 redis的列表结构操作起来非常灵活 应用场景非常广 lpush命令 从列表左边插入多个元素 格式 lpush key valu
  • 使用dockerfile发布go项目

    docker安装 下载docker yum install docker 设置docker随系统启动 chkconfig docker on 启动docker服务 service docker start 启动后查看docker状态 sys
  • pandas Cannot interpolate with all object-dtype columns

    pandas 不能使用所有的 object 类型的列进行插值 这意味着在使用 pandas 的插值函数 例如 interpolate 时 所有的列都必须是数值类型的 如果其中有一列是 object 类型的 则会抛出异常 要解决这个问题 你需
  • 单列模式--Singleton with Go

    package main import fmt sync Singleton 单例对象的类必须保证只有一个实例存在 全局有唯一接口访问 1 懒汉模式 指全局的单例实例在第一次被使用是构建 缺点 非线程安全 type singleton st
  • 软件测试必备的Linux知识(一)

    1 Linux 概述 1 1 测试人员为什么学习linux 对于软件测试人员来说 我们测试的任何产品都是基于操作系统 比如我们每天都在使用的QQ软件 它有windows ios Android Mac OS等版本 需要把QQ安装在各个平台上
  • 虚拟地址,虚拟地址空间, 交换分区

    1 虚拟内存是内存管理的一种方式 它在磁盘上划分出一块空间由操作系统管理 当物理内存耗尽是充当物理内存来使用 它将多个物理内存碎片和部分磁盘空间重定义为连续的地址空间 以此让程序认为自己拥有连续可用的内存 当物理内存不足时 操作系统会将处于
  • 从零开始的ESP8266探索(11)-定时任务调度器Ticker使用演示

    文章目录 目的 使用演示 基本使用1 基本使用2 动态设置和参数传递 停止和重启任务 注意事项 总结 目的 Arduino for esp8266属于无操作系统环境 对于开发多任务的复杂应用还是比较麻烦的 所以这里就提供了一个近似于操作系统
  • bugku No one knows regex better than me

    进入环境是一串php代码 代码审计 考的是正则
  • 感知器的数学表达和训练算法

    目录 一 感知器模型 二 两种训练法则 1 感知器训练法则 2 delta法则 三 小结 1 标准梯度下降算法与随机梯度下降算法的差异 2 有阈值的感知机算法和无阈值的感知机算法的差异 在人工神经网络简介一节中 图二中的ANN系统的每一个单
  • Numpy常用的数据结构

    numpy安装 pip install numpy numpy底层是使用C语言来实现运算的效果非常高 数据清洗的意义 现实生活中 数据并非完美 需要进行清洗才能进行后面的数据分析 数据清洗是整个数据分析项目最消耗时间的一步 数据的质量最终决
  • Java回调函数的理解

    2008 07 21 20 34 所谓回调 就是客户程序C调用服务程序S中的某个函数A 然后S又在某个时候反过来调用C中的某个函数B 对于C来说 这个B便叫做回调函数 例如Win32下的窗口过程函数就是一个典型的回调函数 一般说来 C不会自
  • DC/DC:闭环控制的升压(Boost)变换电路原理设计及实验仿真

    与降压 Buck 变换器类似 升压Boost变换电路也可以构建电压闭环反馈控制的闭环Boost变换器 Boost功率电路图如图所示 具体电路元器件参数计算可参考前期文章 直流电路中升压电路 Boost 的设计原理 参数计算及MATLAB仿真
  • Python实现FA萤火虫优化算法优化支持向量机分类模型(SVC算法)项目实战

    说明 这是一个机器学习实战项目 附带数据 代码 文档 视频讲解 如需数据 代码 文档 视频讲解可以直接到文章最后获取 1 项目背景 萤火虫算法 Fire fly algorithm FA 由剑桥大学Yang于2009年提出 作为最新的群智能
  • 爬虫需要知道的基础

    一 爬虫概述 1 爬虫必须知道的要素 爬虫要遵循网上的爬虫机器人协议 怎样查看 在网址后面加上 robots txt来查看 可以查到哪些是允许的 哪些是不允许的 爬虫的基本步骤 找到网址 发起请求 解析页面并获得原始数据 对数据进行处理 保
  • [深入研究4G/5G/6G专题-23]: 5G NR开机流程4.1 - 随机接入请求消息MSG1与PRACH首个上行信道的调度、时间提前量TA的检测

    作者主页 文火冰糖的硅基工坊 文火冰糖 王文兵 的博客 文火冰糖的硅基工坊 CSDN博客 本文网址 目录 前言 前置条件 第1章 随机接入知识准备
  • Java 到底是值传递还是引用传递?

    在开始深入讲解之前 有必要纠正一下大家以前的那些错误看法了 如果你有以下想法 那么你有必要好好阅读本文 错误理解一 值传递和引用传递 区分的条件是传递的内容 如果是个值 就是值传递 如果是个引用 就是引用传递 错误理解二 Java是引用传递