作者:Intopass
链接:https://www.zhihu.com/question/31203609/answer/50992895
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

首先,不要纠结于 Pass By Value 和 Pass By Reference 的字面上的意义,否则很容易陷入所谓的“一切传引用其实本质上是传值”这种并不能解决问题无意义论战中。
更何况,要想知道Java到底是传值还是传引用,起码你要先知道传值和传引用的准确含义吧?可是如果你已经知道了这两个名字的准确含义,那么你自己就能判断Java到底是传值还是传引用。
这就好像用大学的名词来解释高中的题目,对于初学者根本没有任何意义。

一、搞清楚 基本类型 和 引用类型的不同之处

int num = 10;
String str = "hello";

<img data-rawheight="458" src="https://pic3.zhimg.com/50/166032bc90958c21604110441ad03f45_hd.jpg" data-size="normal" data-rawwidth="728" class="origin_image zh-lightbox-thumb" width="728" data-original="https://pic3.zhimg.com/166032bc90958c21604110441ad03f45_r.jpg">

如图所示,num是基本类型,值就直接保存在变量中。而str是引用类型,变量中保存的只是实际对象的地址。一般称这种变量为"引用",引用指向实际对象,实际对象中保存着内容。

二、搞清楚赋值运算符(=)的作用

num = 20;
str = "java";

<img data-rawheight="572" src="https://pic4.zhimg.com/50/287c0efbb179638cf4cf27cbfdf3e746_hd.jpg" data-size="normal" data-rawwidth="714" class="origin_image zh-lightbox-thumb" width="714" data-original="https://pic4.zhimg.com/287c0efbb179638cf4cf27cbfdf3e746_r.jpg">

对于基本类型 num ,赋值运算符会直接改变变量的值,原来的值被覆盖掉。
对于引用类型 str,赋值运算符会改变引用中所保存的地址,原来的地址被覆盖掉。但是原来的对象不会被改变(重要)。
如上图所示,"hello" 字符串对象没有被改变。(没有被任何引用所指向的对象是垃圾,会被垃圾回收器回收)

三、调用方法时发生了什么?参数传递基本上就是赋值操作

第一个例子:基本类型
void foo(int value) {
value = 100;
}
foo(num); // num 没有被改变
第二个例子:没有提供改变自身方法的引用类型
void foo(String text) {
text = "windows";
}
foo(str); // str 也没有被改变
第三个例子:提供了改变自身方法的引用类型
StringBuilder sb = new StringBuilder("iphone");
void foo(StringBuilder builder) {
builder.append("4");
}
foo(sb); // sb 被改变了,变成了"iphone4"。
第四个例子:提供了改变自身方法的引用类型,但是不使用,而是使用赋值运算符。
StringBuilder sb = new StringBuilder("iphone");
void foo(StringBuilder builder) {
builder = new StringBuilder("ipad");
}
foo(sb); // sb 没有被改变,还是 "iphone"。

重点理解为什么,第三个例子和第四个例子结果不同?

下面是第三个例子的图解:

<img data-rawheight="398" src="https://pic2.zhimg.com/50/d8b82e07ea21375ca6b300f9162aa95f_hd.jpg" data-size="normal" data-rawwidth="772" class="origin_image zh-lightbox-thumb" width="772" data-original="https://pic2.zhimg.com/d8b82e07ea21375ca6b300f9162aa95f_r.jpg">

builder.append("4")之后

<img data-rawheight="424" src="https://pic1.zhimg.com/50/ff2ede9c6c55568d42425561f25a0fd7_hd.jpg" data-size="normal" data-rawwidth="696" class="origin_image zh-lightbox-thumb" width="696" data-original="https://pic1.zhimg.com/ff2ede9c6c55568d42425561f25a0fd7_r.jpg">

下面是第四个例子的图解:

<img data-rawheight="398" src="https://pic2.zhimg.com/50/d8b82e07ea21375ca6b300f9162aa95f_hd.jpg" data-size="normal" data-rawwidth="772" class="origin_image zh-lightbox-thumb" width="772" data-original="https://pic2.zhimg.com/d8b82e07ea21375ca6b300f9162aa95f_r.jpg">

builder = new StringBuilder("ipad"); 之后

<img data-rawheight="438" src="https://pic1.zhimg.com/50/46fa5f10cc135a3ca087dae35a5211bd_hd.jpg" data-size="normal" data-rawwidth="710" class="origin_image zh-lightbox-thumb" width="710" data-original="https://pic1.zhimg.com/46fa5f10cc135a3ca087dae35a5211bd_r.jpg">


2018年1月31日添加部分内容:

这个答案点赞的不少,虽然当时回答时并没有讲的特别详细,今天就稍微多讲一些各种类型数据在内存中的存储方式。

从局部变量/方法参数开始讲起:

局部变量和方法参数在jvm中的储存方法是相同的,都是在栈上开辟空间来储存的,随着进入方法开辟,退出方法回收。以32位JVM为例,boolean/byte/short/char/int/float以及引用都是分配4字节空间,long/double分配8字节空间。对于每个方法来说,最多占用多少空间是一定的,这在编译时就可以计算好。

我们都知道JVM内存模型中有,stack和heap的存在,但是更准确的说,是每个线程都分配一个独享的stack,所有线程共享一个heap。对于每个方法的局部变量来说,是绝对无法被其他方法,甚至其他线程的同一方法所访问到的,更遑论修改。

当我们在方法中声明一个 int i = 0,或者 Object obj = null 时,仅仅涉及stack,不影响到heap,当我们 new Object() 时,会在heap中开辟一段内存并初始化Object对象。当我们将这个对象赋予obj变量时,仅仅是stack中代表obj的那4个字节变更为这个对象的地址。

数组类型引用和对象:

当我们声明一个数组时,如int[] arr = new int[10],因为数组也是对象,arr实际上是引用,stack上仅仅占用4字节空间,new int[10]会在heap中开辟一个数组对象,然后arr指向它。

当我们声明一个二维数组时,如 int[][] arr2 = new int[2][4],arr2同样仅在stack中占用4个字节,会在内存中开辟一个长度为2的,类型为int[]的数组,然后arr2指向这个数组。这个数组内部有两个引用(大小为4字节),分别指向两个长度为4的类型为int的数组。

<img data-rawheight="740" src="https://pic4.zhimg.com/50/v2-6590cb935ae8bf3b7241cb309fe041d7_hd.jpg" data-size="normal" data-rawwidth="1498" class="origin_image zh-lightbox-thumb" width="1498" data-original="https://pic4.zhimg.com/v2-6590cb935ae8bf3b7241cb309fe041d7_r.jpg">

所以当我们传递一个数组引用给一个方法时,数组的元素是可以被改变的,但是无法让数组引用指向新的数组。

你还可以这样声明:int[][] arr3 = new int[3][],这时内存情况如下图

<img data-rawheight="656" src="https://pic1.zhimg.com/50/v2-fdc86227021d56a02b559d6485983c71_hd.jpg" data-size="normal" data-rawwidth="1408" class="origin_image zh-lightbox-thumb" width="1408" data-original="https://pic1.zhimg.com/v2-fdc86227021d56a02b559d6485983c71_r.jpg">

你还可以这样 arr3[0] = new int [5]; arr3[1] = arr2[0];

<img data-rawheight="1026" src="https://pic3.zhimg.com/50/v2-fdc5e737a95d625a47d66ab61e4a2f55_hd.jpg" data-size="normal" data-rawwidth="1758" class="origin_image zh-lightbox-thumb" width="1758" data-original="https://pic3.zhimg.com/v2-fdc5e737a95d625a47d66ab61e4a2f55_r.jpg">

关于String:

原本回答中关于String的图解是简化过的,实际上String对象内部仅需要维护三个变量,char[] chars, int startIndex, int length。而chars在某些情况下是可以共用的。但是因为String被设计成为了不可变类型,所以你思考时把String对象简化考虑也是可以的。

String str = new String("hello")

<img data-rawheight="628" src="https://pic1.zhimg.com/50/v2-a143d0a3594d06f54c6853c46c429e08_hd.jpg" data-size="normal" data-rawwidth="1394" class="origin_image zh-lightbox-thumb" width="1394" data-original="https://pic1.zhimg.com/v2-a143d0a3594d06f54c6853c46c429e08_r.jpg">

当然某些JVM实现会把"hello"字面量生成的String对象放到常量池中,而常量池中的对象可以实际分配在heap中,有些实现也许会分配在方法区,当然这对我们理解影响不大。

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

  1. java参数传递时到底是值传递还是引用传递

    java参数传递时到底是值传递还是引用传递(baidu搜集) 问”,很多人的BLOG里都引用这些面试题,最近因为工作内容比较枯燥,也来看看这些试题以调节一下口味,其中有一道题让我很费解. 原题是:当一 ...

  2. Java调用函数传递参数到底是值传递还是引用传递

    今天翻看微信上有关Java技术的公众号时,看到了一篇关于Java中值传递的问题,文章讨论了在Java中调用函数进行传参的时候到底是值传递还是引用传递这个面试时会问到的问题.之前也接触过类似的问题,但只 ...

  3. Java中到底是值传递还是引用传递?

    Java中到底是值传递还是引用传递? 我们先回顾一下基本概念 实参和形参 参数在编程语言中是执行程序需要的数据,这个数据一般保存在变量中.在Java中定义一个方法时,可以定义一些参数, 举个例子: p ...

  4. 188W+程序员关注过的问题:Java到底是值传递还是引用传递?

    在逛 Stack Overflow 的时候,发现了一些访问量像阿尔卑斯山一样高的问题,比如说这个:Java 到底是值传递还是引用传递?访问量足足有 188万+,这不得了啊!说明有很多很多的程序员被这个 ...

  5. [转帖]Stack Overflow上188万浏览量的提问:Java 到底是值传递还是引用传递?

    Stack Overflow上188万浏览量的提问:Java 到底是值传递还是引用传递? http://www.itpub.net/2019/12/03/4567/   在逛 Stack Overfl ...

  6. JAVA方法中参数到底是值传递还是引用传递

    当一个对象被当作参数传递到一个方法后,在此方法内可以改变这个对象的属性,那么这里到底是值传递还是引用传递? 答:是值传递.Java 语言的参数传递只有值传递.当一个实例对象作为参数被传递到方法中时,参 ...

  7. 面试官:兄弟,说说Java到底是值传递还是引用传递

    二哥,好久没更新面试官系列的文章了啊,真的是把我等着急了,所以特意过来催催.我最近一段时间在找工作,能从二哥的文章中学到一点就多一点信心啊! 说句实在话,离读者 trust you 发给我这段信息已经 ...

  8. 【Java思考】Java 中的实参与形参之间的传递到底是值传递还是引用传递呢?

    科普: 值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数. 引用传递(pass by reference)是指在 ...

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

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

随机推荐

  1. spring使用c3p0报错

    java.sql.SQLException: Connections could not be acquired from the underlying database! at com.mchang ...

  2. mybatis-自定义typeHandler

    场景一:有个java.util.Date在存入数据库的时候自动转换为timestamp时间戳,从数据库取值的时候把时间戳自动转换为java.util.Date 表结构 CREATE TABLE `us ...

  3. 爬虫_vs_反爬虫

    爬虫中有哪些专业术语? 爬虫:自动获取网站数据的程序,关键是批量的获取 反爬虫:使用技术手段防止爬虫程序的方法 误伤:反爬虫技术将普通用户识别为爬虫,效果再好也不能用(禁止ip) 成本:反爬虫需要人力 ...

  4. Servlet--ServletContext接口

    Servlet--ServletContext接口 定义public interface ServletContext 定义了一个 Servlet 的环境对象,通过这个对象,Servlet 引擎向 S ...

  5. 无废话XML--DOM4J

    Dom4j  是一个易用的.开源的库,用于 XML ,XPath  和 XSLT .它应用于 Java  平台,采用了 Java  集合框架并完全支持 DOM ,SAX 和 和 JAXP .我们可以很 ...

  6. 【Java框架型项目从入门到装逼】第十五节 - jdbc模糊查询实现(附带详细调试过程)

    上一节,我们实现了用户列表查询,已经按条件精确查询: if(student.getUsername() != null && !"".equals(student. ...

  7. Python之Django rest_Framework框架源码分析

    #!/usr/bin/env python # -*- coding:utf-8 -*- from rest_framework.views import APIView from rest_fram ...

  8. 完全卸载SQL Server 2008r2

    完全卸载SQL Server 2008r2   "五一"时进行了系统重装,在没有卸载SQL Server 2008情况下尝试安装SQL Server 2008r2 ,安装提示成功但 ...

  9. use zlib lib to compress or decompress file

    If you want to compress or decompress file when writing C++ code,you can choose zlib library,that's ...

  10. linux下安装phpunit简单方法

    现在安装phpunit相当简单,只需要下载phar压缩格式的phpunit文件,给个执行权限,就可以执行了 以下是一段官方安装文档 wget https://phar.phpunit.de/phpun ...