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

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

关于 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 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3169 引用 • 8207 回帖

相关帖子

欢迎来到这里!

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

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

    写的还可以,不错[em01][em01]

  • someone

    [em00]

  • someone

    互相学习~

  • someone

    [em01]

  • someone

    [em00]

  • someone

    测试下评论url。

  • someone

    评论url有问题,折腾了好久。。

请输入回帖内容 ...

推荐标签 标签

  • 安装

    你若安好,便是晴天。

    128 引用 • 1184 回帖
  • 资讯

    资讯是用户因为及时地获得它并利用它而能够在相对短的时间内给自己带来价值的信息,资讯有时效性和地域性。

    53 引用 • 85 回帖 • 1 关注
  • Spark

    Spark 是 UC Berkeley AMP lab 所开源的类 Hadoop MapReduce 的通用并行框架。Spark 拥有 Hadoop MapReduce 所具有的优点;但不同于 MapReduce 的是 Job 中间输出结果可以保存在内存中,从而不再需要读写 HDFS,因此 Spark 能更好地适用于数据挖掘与机器学习等需要迭代的 MapReduce 的算法。

    74 引用 • 46 回帖 • 548 关注
  • 锤子科技

    锤子科技(Smartisan)成立于 2012 年 5 月,是一家制造移动互联网终端设备的公司,公司的使命是用完美主义的工匠精神,打造用户体验一流的数码消费类产品(智能手机为主),改善人们的生活质量。

    4 引用 • 31 回帖 • 6 关注
  • React

    React 是 Facebook 开源的一个用于构建 UI 的 JavaScript 库。

    192 引用 • 291 回帖 • 441 关注
  • 知乎

    知乎是网络问答社区,连接各行各业的用户。用户分享着彼此的知识、经验和见解,为中文互联网源源不断地提供多种多样的信息。

    10 引用 • 66 回帖
  • 安全

    安全永远都不是一个小问题。

    189 引用 • 813 回帖 • 1 关注
  • WebComponents

    Web Components 是 W3C 定义的标准,它给了前端开发者扩展浏览器标签的能力,可以方便地定制可复用组件,更好的进行模块化开发,解放了前端开发者的生产力。

    1 引用 • 24 关注
  • 京东

    京东是中国最大的自营式电商企业,2015 年第一季度在中国自营式 B2C 电商市场的占有率为 56.3%。2014 年 5 月,京东在美国纳斯达克证券交易所正式挂牌上市(股票代码:JD),是中国第一个成功赴美上市的大型综合型电商平台,与腾讯、百度等中国互联网巨头共同跻身全球前十大互联网公司排行榜。

    14 引用 • 102 回帖 • 408 关注
  • etcd

    etcd 是一个分布式、高可用的 key-value 数据存储,专门用于在分布式系统中保存关键数据。

    5 引用 • 26 回帖 • 496 关注
  • Tomcat

    Tomcat 最早是由 Sun Microsystems 开发的一个 Servlet 容器,在 1999 年被捐献给 ASF(Apache Software Foundation),隶属于 Jakarta 项目,现在已经独立为一个顶级项目。Tomcat 主要实现了 JavaEE 中的 Servlet、JSP 规范,同时也提供 HTTP 服务,是市场上非常流行的 Java Web 容器。

    162 引用 • 529 回帖 • 1 关注
  • RYMCU

    RYMCU 致力于打造一个即严谨又活泼、专业又不失有趣,为数百万人服务的开源嵌入式知识学习交流平台。

    4 引用 • 6 回帖 • 41 关注
  • 以太坊

    以太坊(Ethereum)并不是一个机构,而是一款能够在区块链上实现智能合约、开源的底层系统。以太坊是一个平台和一种编程语言 Solidity,使开发人员能够建立和发布下一代去中心化应用。 以太坊可以用来编程、分散、担保和交易任何事物:投票、域名、金融交易所、众筹、公司管理、合同和知识产权等等。

    34 引用 • 367 回帖 • 3 关注
  • abitmean

    有点意思就行了

    24 关注
  • 工具

    子曰:“工欲善其事,必先利其器。”

    276 引用 • 685 回帖
  • jsoup

    jsoup 是一款 Java 的 HTML 解析器,可直接解析某个 URL 地址、HTML 文本内容。它提供了一套非常省力的 API,可通过 DOM,CSS 以及类似于 jQuery 的操作方法来取出和操作数据。

    6 引用 • 1 回帖 • 462 关注
  • Docker

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

    478 引用 • 902 回帖
  • 互联网

    互联网(Internet),又称网际网络,或音译因特网、英特网。互联网始于 1969 年美国的阿帕网,是网络与网络之间所串连成的庞大网络,这些网络以一组通用的协议相连,形成逻辑上的单一巨大国际网络。

    96 引用 • 330 回帖
  • WebClipper

    Web Clipper 是一款浏览器剪藏扩展,它可以帮助你把网页内容剪藏到本地。

    3 引用 • 9 回帖 • 2 关注
  • Hexo

    Hexo 是一款快速、简洁且高效的博客框架,使用 Node.js 编写。

    21 引用 • 140 回帖 • 30 关注
  • 黑曜石

    黑曜石是一款强大的知识库工具,支持本地 Markdown 文件编辑,支持双向链接和关系图。

    A second brain, for you, forever.

    10 引用 • 85 回帖
  • Ngui

    Ngui 是一个 GUI 的排版显示引擎和跨平台的 GUI 应用程序开发框架,基于
    Node.js / OpenGL。目标是在此基础上开发 GUI 应用程序可拥有开发 WEB 应用般简单与速度同时兼顾 Native 应用程序的性能与体验。

    7 引用 • 9 回帖 • 345 关注
  • Hprose

    Hprose 是一款先进的轻量级、跨语言、跨平台、无侵入式、高性能动态远程对象调用引擎库。它不仅简单易用,而且功能强大。你无需专门学习,只需看上几眼,就能用它轻松构建分布式应用系统。

    9 引用 • 17 回帖 • 598 关注
  • SVN

    SVN 是 Subversion 的简称,是一个开放源代码的版本控制系统,相较于 RCS、CVS,它采用了分支管理系统,它的设计目标就是取代 CVS。

    29 引用 • 98 回帖 • 693 关注
  • FFmpeg

    FFmpeg 是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。

    22 引用 • 31 回帖 • 2 关注
  • 国际化

    i18n(其来源是英文单词 internationalization 的首末字符 i 和 n,18 为中间的字符数)是“国际化”的简称。对程序来说,国际化是指在不修改代码的情况下,能根据不同语言及地区显示相应的界面。

    7 引用 • 26 回帖 • 5 关注
  • LaTeX

    LaTeX(音译“拉泰赫”)是一种基于 ΤΕΧ 的排版系统,由美国计算机学家莱斯利·兰伯特(Leslie Lamport)在 20 世纪 80 年代初期开发,利用这种格式,即使使用者没有排版和程序设计的知识也可以充分发挥由 TeX 所提供的强大功能,能在几天,甚至几小时内生成很多具有书籍质量的印刷品。对于生成复杂表格和数学公式,这一点表现得尤为突出。因此它非常适用于生成高印刷质量的科技和数学类文档。

    9 引用 • 32 回帖 • 160 关注