笔者的个人博客 Bridge for You 已经上线,欢迎大家前去坐坐,喝茶侃大山!

(这篇文章好像挺受欢迎的,借势给自己的个人网站做做宣传 (〃'▽'〃))

前言

在开始看我画小狗之前,咱们先来看道很简单的题目:

下面程序的输出是什么?

Dog myDog = new Dog("旺财");

changeName(myDog);

System.out.println(myDog.getName());

public void changeName(Dog dog) {

dog.setName("小强");

}

如果你回答事“小强”,好,恭喜你答对了。下面我们改一下代码:

Dog myDog = new Dog("旺财");

changeName(myDog);

System.out.println(myDog.getName());

public void changeName(Dog dog) {

dog = new Dog();

dog.setName("小强");

}

是的,我只是在changeName方法里面加了一句代码

dog = new Dog();

这一次的输出又是什么呢?A 旺财

B 小强

答案是A旺财,changeName方法并没有把myDog的名称改了。如果你答错了,没关系,我要开始画小狗了,画完你就明白了;如果你答对了,但不太明白其中的原因,那我画的小狗也肯定能帮到你。

myDog是什么

首先你要搞懂,代码里的变量myDog是什么?myDog真的就是一只狗吗?不!不是!myDog只是一条遛狗用的狗绳!

换句话说说,myDog并不是new出来的放在堆中的对象(object)!myDog只是一个指向这个对象实例的引用(reference)!如果你对Java的运行时数据区域足够了解,应该知道,这个引用是放在虚拟机栈上的。

参数传递

现在你知道了,myDog只是一条绳子,但这似乎并不能解释为什么changeName方法没有把myDog的名称改为小强,因为按照现有的理解,dog = new Dog(),就是把我的狗绳绑到另一只小狗身上,然后给这只小狗起名为小强,就像这样:

可事实是,myDog还是叫旺财,这是为什么?

问题就出在方法调用上,当我执行changeName(myDog)这一行代码时,myDog这条狗绳,被复制了一份,传入到changeName方法里的那条狗绳(dog),就是复制出来的那一条,就像这样:

接着执行dog= new Dog(),这一行代码,就是把复制出来的那一条狗绳,从myDog解绑,重新绑到new出来的那只小狗上,也就是后来被起名为小强的小狗:

而myDog还是绑在旺财身上,这也就解释了,为什么执行完方法出来,myDog.getName()还是旺财。而在第一段代码里面,我们没有执行dog= new Dog(),也就没有改变dog所绑的小狗,dog还是绑在旺财身上,因此dog.setName("小强") 就把旺财的名字改成小强了。

应 @李阿哥 的建议,在这里小小的总结一下:方法内部将变量指向新开的这个内存之后,修改方法内部的变量,方法外部的变量不会受到影响。

方法内部如果没有改变变量指向的内存,那么由于仍然和方法外部的变量指向同一个对象,因此修改方法内部的变量,外部的变量也会受到影响。

string的例子

我们再来看一个例子:

String str = "aaa";

changeString(str);

System.out.println(str);

public void changeString(String str) {

str = "bbb";

}

如果你弄懂了上面那个例子,那么这里应该不难理解,changeString方法里,只是将新复制出来的引用str,指向另外一个字符串常量对象“bbb”,方法体外面的str并不受影响,还是指向字符串常量“aaa”,因此最终打印的还是aaa.

int的例子

上面提到的都是对象,下面看一个基本数据类型的例子

int i = 1;

changeInt(i);

System.out.println(i);

public void changeInt(int i) {

i = 2;

}

对于基本数据类型,他们没有引用,但是不要忘了,调用函数时,复制的动作还是会做的,执行changeInt(i)时,会将 i 复制到一个新的int上,传给changeInt方法,因此不管changeInt内部对入参做了什么,外面的 i 都不会受影响。最后打印出来的还是1.

值传递和引用传递

上面提到的参数传递过程中的复制操作,说白了,就是 = 操作。把上面那个int例子,做一下方法内联,其实就是这样:

int i = 1;

// 方法内联,相当于执行changeInt方法int j = i; // 新建一个和i一样的变量j = 2; //修改j的值,i不变

System.out.println(i);

对于基本数据类型,= 操作将右边的变量(R_VALUE)完整的赋值给左边的变量(L_VALUE),而对于对象,准确的说,应该是指向对象的引用(就像上面说的myDog),= 操作同样也是将右边的引用完整的复制给左边的引用,两者指向同一个对象实例。

这个 = 操作,是值传递和引用传递的根本差别,这也导致了值传递和引用传递有以下直观上的差别:如果参数是值传递,那么调用者(方法体外部)和被调用者(方法体内部)用的是两个不同的变量,方法体里面对变量的改动不会影响方法体外面的变量。而之所以在Java可以在方法体内部改变方法体外部的对象,是因为方法体内部拿到了对象的引用,但是这个引用是和方法体外部的引用属于两个不同的引用的,方法体内部的引用指向别的对象,不会导致方法体外部的引用也指向别的对象。

如果参数是引用传递,那么调用者(方法体外部)和被调用者(方法体内部)用的是两个相同的变量,方法体里面对变量的改动会影响方法体外面的变量。

Java的变量都不是对象

通过上面的讲解,你也知道了一个很重要的点:Java里面的变量,要么是基本数据类型,要么是指向对象实例的引用类型(狗绳),绝对不会是一个对象(狗)。

狗绳和垃圾回收

弄懂了myDog只是一条狗绳(引用),也有助于我们理解Java的垃圾回收机制,我在另一篇文章里提到过,一旦JVM发现一个对象跟GC Roots不可达时,这个对象就会被回收掉,看一下下面这段代码:

Dog dog = new Dog();

dog = null;

现在我们知道,dog=null就等于是把狗绳给咔嚓减掉了,这样狗就跑了,变成流浪狗了,就像Java中的对象被当做垃圾回收了一样。

接着再来看一下交叉引用的例子:

Dog dog1 = new Dog();

Dog dog2 = new Dog();

dog1.son = dog2;

dog2.father = dog1;

dog1 = null;

dog2 = null;

如果JVM采用的是引用计数法,那么狗2原先被dog2和dog1.son两个变量引用这,执行完dog2 = null之后,还被dog1.son引用,狗2是不会被回收的。

但是如果使用可达性分析法,我们就会发现,这两只狗和这个世界已经没有关联了,尽管他们俩还是父子关系,JVM对于这种互相引用,但是和GC ROOTS已经没有关联的对象,照样会进行回收。

引用传递的替代方法

引用传递有两个好处:引用传递可以避免调用方法时进行拷贝,尤其是当方法的入参是个大对象时,拷贝会耗费大量的时间和空间,当然,这一点Java已经巧妙地解决了,因为对于对象,拷贝的只是它的引用而已;

引用传递可以对外面的对象进行修改,这也是很多语言支持引用传递的原因。

那么,在Java,要怎么实现“对外面的对象进行修改”类似的功能呢?

答案是使用返回值,类似这样:

a = doSomeThing(a);

当然,如果你只是对一个对象进行修改,然后返回这个对象的新的版本,那么可以考虑把这个方法挪到这个对象里面去,就像这样:

a = a.doSomeThing();

还有,如果你是需要返回多个值,不使用引用传递,要如何实现?

答案是返回一个对象,比如你想修改一个地方的经度和纬度,那么与其传入log和lat两个变量,不如把他们封装到Point对象里面去。

本文示例中的完整代码,可以到"Bridge for You"的GitHub上下载。

以上,希望对你有所帮助。

参考内容

用java如何画动物_用画小狗的方法来解释Java值传递相关推荐

  1. 用画小狗的方法来解释Java中的值传递

    在开始看我画小狗之前,咱们先来看道很简单的题目: 下面程序的输出是什么? 如果你的回答是"小强",好,恭喜你答对了.下面我们改一下代码: 是的,我只是在changeName方法里面 ...

  2. 刻画小狗状态java_用画小狗的方法来解释Java中的值传递

    在开始看我画小狗之前,咱们先来看道很简单的题目: 下面程序的输出是什么? Dog myDog = new Dog("旺财"); changeName(myDog); System. ...

  3. Java中,String类型和包装类型作为参数传递时,是属于值传递还是引用传递呢?...

    <Java中,String类型和包装类型作为参数传递时,是属于值传递还是引用传递呢?> <Java中的值传递和引用传递> 原理知识: 如果参数类型是原始类型,那么传过来的就是这 ...

  4. [转载] java中对象作为参数传递给一个方法,到底是值传递,还是引用传递

    参考链接: 用Java传递和返回对象 看完绝对清晰~ java中对象作为参数传递给一个方法,到底是值传递,还是引用传递? pdd:所谓java只有按值传递:基本类型  值传递:引用类型,地址值传递,所 ...

  5. 如何用catia画半圆_简笔画用半圆画卡通动物

    [创意系列-半圆动物] ◁第一步 首先画出三个半圆形.大小是一样的. 第二步▷ 在第一个半圆上画出一对尖尖的耳朵. ◁第三步 然后画出它的表情.眼睛留出高光部分. 第四步▷ 还有一双小小的手臂.可爱的 ...

  6. java左手画圆右手画方_左手画圆,右手画方作文

    左手画圆,右手画方作文 无论是在学校还是在社会中,大家都经常接触到作文吧,作文是从内部言语向外部言语的过渡,即从经过压缩的简要的.自己能明白的语言,向开展的.具有规范语法结构的.能为他人所理解的外部语 ...

  7. java左手画圆右手画方_左手画圆、右手画方,双手齐用同时养护、开发你的左右大脑!...

    原标题:左手画圆.右手画方,双手齐用同时养护.开发你的左右大脑! 如果有机会让你学到武侠小说中的绝世武功,你会学哪一种? 什么九阴真经.九阳神功.独孤九剑.降龙十八掌......每一种都是让人神往的绝 ...

  8. java左手画圆右手画方_左手画圆,右手画方真的很难吗?为什么人们很难做到呢?...

    导语:左手画圆,右手画方真的很难吗?为什么人们很难做到呢? 我们经常会看到有些人能够实现左手画圆.右手画方的这一个行为,并且是可以通过这样的方式来训练自己身体的协调能力的,那么当我们自己去尝试的时候, ...

  9. java左手画圆右手画方_左手画圆,右手画方,有两个截然不同的说法,你知道吗?...

    说到金庸先生的武侠小说,其中对于中国古典传统文化的引用,神乎其技.小说中的各种武侠招式,我们都可以从古典文籍中找到影子.比如丐帮帮主洪七公,传授给穆念慈的逍遥游,我们就会联想到庄子的<逍遥游&g ...

最新文章

  1. arm服务器配置信息,ARM板 web服务器交叉编译及配置
  2. Facebook 开源了一整套重要的 Linux 内核组件与工具!
  3. SAP RETAIL MP30为物料Execute Forecast,报错- Status Forecast not defined –
  4. axios get 某个参数是数组怎么传_Vue 中 Axios 的封装和 API 接口的管理
  5. 波卡链Substrate (7)Babe协议四“出块签名和验证”
  6. sklearn集成学习概述
  7. 持久化技术SharedPreferences存储
  8. Data-structures-and-algorithms-interview-questions-and-their-solutions
  9. linux ps ax tl,Linux常用指令 - osc_wa6fkyf0的个人空间 - OSCHINA - 中文开源技术交流社区...
  10. 学历全靠编,融资靠忽悠?网传“包养7个女主持”的金融大佬被揭穿了
  11. 统一操作系统 UOS 官网正式上线;旷视回应“戴口罩人脸识别”;IntelliJ IDEA 2019.3.3 发布 | 极客头条...
  12. “wget”不是内部或外部命令,也不是可运行的程序或批处理文件
  13. How to update a module
  14. JavaScript自学笔记 第5次
  15. iphone6连接电脑后计算机不显示器,苹果手机怎么连接电脑没反应
  16. 霹雳吧啦Wz语义分割学习笔记P5
  17. MOSES统计机器翻译系统实验过程
  18. API接口:公司信息查询在线工具应用
  19. 东北大学计算机保研清华难吗,学霸宿舍:他们全部保研清华、中科院、东大等名校,值得借鉴!...
  20. 轻松解决CENTOS装完独立显卡也无法显示1920x1080问题

热门文章

  1. 博弈论(van♂游戏) 笔记
  2. 一口气了解【2021 阿里云峰会】重磅发布
  3. 案例解析|自然保护区水资源远程监控方案
  4. 在家就能拍,韩系证件照拍摄教程
  5. 网上流传ldquo;魔方文化启示录rdquo;
  6. tsv文件导入mysql
  7. 如何把苹果文件APP里的Word文档传到手机备忘录
  8. “机器学习实战”刻意练习——分类问题:决策树
  9. win32com处理excel数据透视表格式
  10. Unity-日志工具