Java 值传递与引用传递的一些误区

本贴最后更新于 2828 天前,其中的信息可能已经时移世易

关于 Java 值传递与引用传递的讨论有很多,先来了解一下值传递和引用传递。

值传递:是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

引用传递:是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

概念知道了,接下来看问题:

public static void main(String[] args) { int a = 0; char[] c = {'a', 'b', 'c'}; change(a, c); System.out.println("a:" + a); System.out.println("c[0]:" + c[0]); } private static void change(int a, char[] c) { a = 2; c[0] = '0'; }

输出结果为:

a:0 c[0]:0

这种写法很容易让人产生误解,认为 change 方法里直接对 a 重新赋值,直接对 c[0]重新赋值,我们换个写法:

public static void main(String[] args) { int a = 0; char[] c = {'a', 'b', 'c'}; change(a, c); System.out.println("a:" + a); System.out.println("c[0]:" + c[0]); } /** * 为避免混淆,换个参数名 */ private static void change(int aa, char[] cc) { aa = 2; cc[0] = '0'; }

输出结果为:

a:0 c[0]:0

change 方法里对 aa 的修改并没有影响到 main 方法里 a 的值,所以第一个参数是值传递没有问题。但是对 cc[0]的修改却影响到了 main 方法里 c[0]的值,这是否说明第二个参数是引用传递呢?我们再来修改一下代码:

public static void main(String[] args) { int a = 0; char[] c = {'a', 'b', 'c'}; change(a, c); System.out.println("a:" + a); System.out.println("c[0]:" + c[0]); } /** * 为避免混淆,换个参数名 */ private static void change(int aa, char[] cc) { aa = 2; cc = null; }

输出结果为:

a:0 c[0]:a

由此可知,对 cc 的修改其实并不会影响 main 方法里的 c。那么刚才为什么可以影响呢?因为调用 change 方法时,我们把 c 当做参数传了进去,而在 change 方法里,又有一个形参 cc,此时这两个引用同时指向同一个内存地址,也就是说,这时候不管用哪个引用去操作这块内存区域的数组,都会导致数组发生变化,但是请注意,这里说的是对数组进行操作,如果在 change 方法里把 cc 赋值为 null,这个就不是对数组进行操作了,而是把 cc 由指向数组变为指向 null,相当于有两个变量,一个是 c,一个是 cc,不管把 cc 赋值成什么,都与 c 无关。所以,这里并没有引用传递,也是值传递。

再来看一个进阶版:

public static void main(String[] args) { String str = "abc"; char[] c = {'a', 'b', 'c'}; change(str, c); System.out.println("str:" + str); System.out.println("c[0]:" + c[0]); } /** * 为避免混淆,换个参数名 */ private static void change(String s, char[] cc) { s = "xyz"; cc[0] = '0'; }

输出结果为:

str:abc c[0]:0

两个参数都是引用数据类型,怎么会一个改变了,一个没有改变呢?有些解释是,string 是 final 修饰的,不能改变,所以虽然 string 是引用数据类型,但是它是个特例,是值传递,但是数组能改变,是引用传递。通过上面的分析,我们已经知道,不管是引用数据类型,还是基本数据类型,其参数传递都是值传递,但是引用数据类型是可以改变其属性的,那为什么 string 没有改变,难道真的是特例吗?

我们都知道,字符串其实也就相当于是复杂点的 char 数组,仔细观察会发现,change 方法里面对字符串 s 的操作其实是相当于改变其引用,并没有对字符串里的数组进行属性改变,而对数组 cc 的操作却是改变数组的属性,也就是说,这两个操作根本不在一个层面。要么就都改变引用,要么就都改变属性,操作保证一致,才能够说明问题。

修改代码,使 change 改变属性:

public static void main(String[] args) throws Exception { String str = "abc"; char[] c = {'a', 'b', 'c'}; change(str, c); System.out.println("str:" + str); System.out.println("c[0]:" + c[0]); } /** * 为避免混淆,换个参数名 */ private static void change(String s, char[] cc) throws Exception { Field valueFieldOfString = String.class.getDeclaredField("value"); valueFieldOfString.setAccessible(true); char[] value = (char[]) valueFieldOfString.get(s); value[0] = '0'; cc[0] = '0'; }

输出结果为:

str:0bc c[0]:0

通过反射获取到 string 的私有 char 数组属性,并修改数组的第一个元素,说明 string 并不是不能修改,也不是什么特例。change 方法影响到了 main 方法里的原始变量,但是原因是我们上面分析的,只是因为两个引用都指向同一个内存地址而已,本质是值传递,并不是引用传递。

修改代码,使 change 改变引用:

public static void main(String[] args) { String str = "abc"; char[] c = {'a', 'b', 'c'}; change(str, c); System.out.println("str:" + str); System.out.println("c[0]:" + c[0]); } /** * 为避免混淆,换个参数名 */ private static void change(String s, char[] cc) { s = null; cc = null; }

输出结果为:

str:abc c[0]:a

并没有影响 main 方法中原始变量,值传递。

通过上面的一系列分析,我们可以确定,Java 中不管是基本数据类型,还是引用数据类型,都只存在值传递,不存在引用传递,更不存在什么特例。

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3194 引用 • 8214 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1063 引用 • 3455 回帖 • 165 关注
  • 心情

    心是产生任何想法的源泉,心本体会陷入到对自己本体不能理解的状态中,因为心能产生任何想法,不能分出对错,不能分出自己。

    59 引用 • 369 回帖 • 2 关注
  • 一些有用的避坑指南。

    69 引用 • 93 回帖
  • 房星科技

    房星网,我们不和没有钱的程序员谈理想,我们要让程序员又有理想又有钱。我们有雄厚的房地产行业线下资源,遍布昆明全城的 100 家门店、四千地产经纪人是我们坚实的后盾。

    6 引用 • 141 回帖 • 592 关注
  • Follow
    4 引用 • 12 回帖 • 6 关注
  • 招聘

    哪里都缺人,哪里都不缺人。

    189 引用 • 1057 回帖
  • Sym

    Sym 是一款用 Java 实现的现代化社区(论坛/BBS/社交网络/博客)系统平台。

    下一代的社区系统,为未来而构建

    524 引用 • 4601 回帖 • 702 关注
  • 域名

    域名(Domain Name),简称域名、网域,是由一串用点分隔的名字组成的 Internet 上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位(有时也指地理位置)。

    43 引用 • 208 回帖 • 2 关注
  • C

    C 语言是一门通用计算机编程语言,应用广泛。C 语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。

    85 引用 • 165 回帖
  • H2

    H2 是一个开源的嵌入式数据库引擎,采用 Java 语言编写,不受平台的限制,同时 H2 提供了一个十分方便的 web 控制台用于操作和管理数据库内容。H2 还提供兼容模式,可以兼容一些主流的数据库,因此采用 H2 作为开发期的数据库非常方便。

    11 引用 • 54 回帖 • 665 关注
  • MyBatis

    MyBatis 本是 Apache 软件基金会 的一个开源项目 iBatis,2010 年这个项目由 Apache 软件基金会迁移到了 google code,并且改名为 MyBatis ,2013 年 11 月再次迁移到了 GitHub。

    173 引用 • 414 回帖 • 367 关注
  • Logseq

    Logseq 是一个隐私优先、开源的知识库工具。

    Logseq is a joyful, open-source outliner that works on top of local plain-text Markdown and Org-mode files. Use it to write, organize and share your thoughts, keep your to-do list, and build your own digital garden.

    7 引用 • 69 回帖 • 1 关注
  • 小薇

    小薇是一个用 Java 写的 QQ 聊天机器人 Web 服务,可以用于社群互动。

    由于 Smart QQ 从 2019 年 1 月 1 日起停止服务,所以该项目也已经停止维护了!

    34 引用 • 467 回帖 • 760 关注
  • 旅游

    希望你我能在旅途中找到人生的下一站。

    93 引用 • 901 回帖
  • SpaceVim

    SpaceVim 是一个社区驱动的模块化 vim/neovim 配置集合,以模块的方式组织管理插件以
    及相关配置,为不同的语言开发量身定制了相关的开发模块,该模块提供代码自动补全,
    语法检查、格式化、调试、REPL 等特性。用户仅需载入相关语言的模块即可得到一个开箱
    即用的 Vim-IDE。

    3 引用 • 31 回帖 • 116 关注
  • 人工智能

    人工智能(Artificial Intelligence)是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门技术科学。

    157 引用 • 290 回帖
  • 996
    13 引用 • 200 回帖 • 2 关注
  • 阿里云

    阿里云是阿里巴巴集团旗下公司,是全球领先的云计算及人工智能科技公司。提供云服务器、云数据库、云安全等云计算服务,以及大数据、人工智能服务、精准定制基于场景的行业解决方案。

    84 引用 • 324 回帖 • 1 关注
  • Docker

    Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的操作系统上。容器完全使用沙箱机制,几乎没有性能开销,可以很容易地在机器和数据中心中运行。

    494 引用 • 928 回帖
  • 阿里巴巴

    阿里巴巴网络技术有限公司(简称:阿里巴巴集团)是以曾担任英语教师的马云为首的 18 人,于 1999 年在中国杭州创立,他们相信互联网能够创造公平的竞争环境,让小企业通过创新与科技扩展业务,并在参与国内或全球市场竞争时处于更有利的位置。

    43 引用 • 221 回帖 • 75 关注
  • CSDN

    CSDN (Chinese Software Developer Network) 创立于 1999 年,是中国的 IT 社区和服务平台,为中国的软件开发者和 IT 从业者提供知识传播、职业发展、软件开发等全生命周期服务,满足他们在职业发展中学习及共享知识和信息、建立职业发展社交圈、通过软件开发实现技术商业化等刚性需求。

    14 引用 • 155 回帖
  • DevOps

    DevOps(Development 和 Operations 的组合词)是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。

    57 引用 • 25 回帖 • 7 关注
  • WordPress

    WordPress 是一个使用 PHP 语言开发的博客平台,用户可以在支持 PHP 和 MySQL 数据库的服务器上架设自己的博客。也可以把 WordPress 当作一个内容管理系统(CMS)来使用。WordPress 是一个免费的开源项目,在 GNU 通用公共许可证(GPLv2)下授权发布。

    66 引用 • 114 回帖 • 200 关注
  • Chrome

    Chrome 又称 Google 浏览器,是一个由谷歌公司开发的网页浏览器。该浏览器是基于其他开源软件所编写,包括 WebKit,目标是提升稳定性、速度和安全性,并创造出简单且有效率的使用者界面。

    62 引用 • 289 回帖 • 1 关注
  • SQLServer

    SQL Server 是由 [微软] 开发和推广的关系数据库管理系统(DBMS),它最初是由 微软、Sybase 和 Ashton-Tate 三家公司共同开发的,并于 1988 年推出了第一个 OS/2 版本。

    21 引用 • 31 回帖 • 2 关注
  • ReactiveX

    ReactiveX 是一个专注于异步编程与控制可观察数据(或者事件)流的 API。它组合了观察者模式,迭代器模式和函数式编程的优秀思想。

    1 引用 • 2 回帖 • 175 关注
  • Gitea

    Gitea 是一个开源社区驱动的轻量级代码托管解决方案,后端采用 Go 编写,采用 MIT 许可证。

    5 引用 • 16 回帖 • 1 关注