Java--传参的值传递和引用传递问题
2015-08-03 23:13
225 查看
今天遇到了一个java程序,需要用参数来返回值(虽然最后用另一种方法实现了),在网上看到这样一篇文章,很受启发。
本文章来自于http://www.cnblogs.com/laipDIDI/articles/2524309.html
首先,推荐对Java有一定理解的同仁一本书《Practical Java》。在《Practical Java》中也有一个章节介绍Java中关于传值和传引用的问题,堪称经典。《Practical Java》在Java中,事实上底层工作原理不存在传引用的概念,这也象《Practical Java》中所说的那样,Java中只有传值。这句话理解起来需要费一定的周折。
熟悉C的程序员都用过指针,对指针可谓爱之深恨之切。指针是指向一块内存地址的内存数据(有些拗口),也就是说指针本身是一个占用4字节内存的int(32 位系统内),而这个int值恰恰又是另一块内存的地址。比如”hello”这个字串,存放在@0x0000F000这个地址到@0x0000F005这段内存区域内(包括0x00的结束字节)。而在@0x0000FFF0到@0x0000FFF03这四个字节内存放着一个int,这个int的值是 @0x0000F000。这样就形成了一个指向”hello”字串的指针。
所谓传引用的说法是为了更好的讲解调用方式。基于上面对指针的理解,我们不难看出,指针其实也是一个int值,所谓传引用,我们是复制了复制了指针的int值进行传递。为了便于理解,我们可以姑且把指针看作一种数据类型,透明化指针的int特性,从而提出传引用的概念。
重申一遍:Java中只有传值。
1所谓传值和传引用
传值和传引用的问题一直是Java里争论的话题。与C++不同的,Java里面没有指针的概念,Java的设计者巧妙的对指针的操作进行了管理。事实上,在懂C++的Java程序员眼中,Java到处都是精美绝伦的指针。
下面举个简单的例子,说明什么是传值,什么是传引用。
所谓传值和传引用
传值和传引用的问题一直是Java里争论的话题。与C++不同的,Java里面没有指针的概念,Java的设计者巧妙的对指针的操作进行了管理。事实上,在懂C++的Java程序员眼中,Java到处都是精美绝伦的指针。
下面举个简单的例子,说明什么是传值,什么是传引用。
很显然的,在mothod1中执行了change(x)后,x的值并不会因为change方法中将输入参数赋值为1而变成1,也就是说在执行change(x)后,x的值z依然是0。这是因为x传递给change(int i)的是值。这就是最简单的传值。
同样的,进行一点简单的变化。
看起来没什么变化,但是这次mothed1中执行了change (x)后,x的值不再是”Hello”了,而是变成了”Hello world!”。这是因为x传递给change(i)的是x的引用。这是最经典的传引用。
似乎有些奇怪了,两段程序没有特别的不同,可是为什么一个传的是值而另一个传的是引用呢?……
2非要搞清楚传值还是传引用的问题吗?
搞清楚这自然是有必要的,不然我也不需要写这么多了,不过的确没有到”非要”的地步。
首先,如果我们不太关心什么是传值什么是传引用,我们一样能写出漂亮的代码,但是这些代码在运行过程中可能会存在着极大的隐患。全局变量是让大家深恶痛绝(又难以割舍)的东西,原因就是使用全局变量要特别注意数据的保护。如果在多线程的程序里使用全局变量简直就等于跟自己过不去。不了解传值和传引用的问题,跟使用全局变量不考虑数据保护的罪过是不相上下下的,甚至有时候比它还要糟。你会莫名其妙,为什么我的返回参数没有起作用,为什么我传进去的参数变成了这样……?
一个例子:
上面是一个交换a,b值的函数,看起来似乎蛮正确的,但是这个函数永远也不会完成你想要的工作。
还有一个例子:
3类型和类
Java 提出的思想,在Java里面任何东西都是类。但是Java里面同时还有简单数据类型int,byte,char,boolean,与这些数据类型相对应的类是Integer,Byte,Character,Boolean,这样做依然不会破坏Java关于任何东西都是类的提法。这里提到数据类型和类似乎和我们要说的传值和传引用的问题无关,但这是我们分辨传值和传引用的基础。
4试图分辨传值还是传引用
为什么是”试图分辨”呢?很简单,传值和传引用的问题无处不在,但是似乎还没有人能正统的给出标准,怎样的就是值拷贝调用,怎样的就是引用调用。面对这个问题,我们更多的应该是来自平时积累对Java的理解。
回过头来,我们分析一下上面的几个例子:
先看例1,即使你不明白为什么,但是你应该知道这样做肯定不会改变x的值。为了方便说明,我们给例子都加上行号。
让我们从内存的存储方式看一下x和I之间到底是什么关系。
在执行到第2行的时候,变量x指向一个存放着int 0的内存地址。
那么,试着分析一下为什么例三中的switchValue()方法不能完成变量值交换的工作?
再看例2。
例2似乎和例1从代码上看不出什么差别,但是执行结果却是change(x)能改变x的值。依然才从内存的存储角度来看看例2的蹊跷在哪里。
在执行到第2行时候,同例1一样,x指向一个存放”Hello”的内存空间。
这几个例子是明白了,可是很多人会开始有另一个疑问了:这样看来,到底什么时候是传的值什么时候是传得引用呢?于是,我们前面讲到的类型和类在这里就派上了用场:对于参数传递,如果是简单数据类型,那么它传递的是值拷贝,对于类的实例它传递的是类的引用。需要注意的是,这条规则只适用于参数传递。为什么这么说呢?我们看看这样一个例子:
这两句执行后,str的内容依然是”abcdefghijk”,但是我们明明是对str操作的,为什么是这样的呢?因为str的值究竟会不会被改变完全取决于replaceAll这个方法是怎么实现的。类似的,有这样一个例子:
调用method1(),屏幕打印结果为:”Hello world!”
调用method2(),我们认为结果应该是”hi world”,因为sb传进来的是引用。可是实际执行的结果是”Hello”!
难道change2()又变成传值了?!其实change1()和change2()的确都是通过参数传入引用,但是在方法内部因为处理方法的不同而使结果大相径庭。我们还是从内存的角度分析:
执行method1()和change1()不用再多说了,上面的例子已经讲解过,这里我们分析一下method2()和change2()。
程序执行到第8行,x指向一个存放着”Hello”的内存空间。
所以,还有一条不成规则的规则:对于函数调用,最终效果是什么完全看函数内部的实现。比较标准的做法是如果会改变引用的内容,则使用void作为方法返回值,而不会改变引用内容的则在返回值中返回新的值。
虽然已经说了这么多,但是感觉传值还是传引用的问题依然没有完全说清楚。因为这个问题本身就是很难归纳总结的问题,所以更多的理解要靠平时的积累和形成。下面几个例子,给大家尝试进行分析。
本文章来自于http://www.cnblogs.com/laipDIDI/articles/2524309.html
首先,推荐对Java有一定理解的同仁一本书《Practical Java》。在《Practical Java》中也有一个章节介绍Java中关于传值和传引用的问题,堪称经典。《Practical Java》在Java中,事实上底层工作原理不存在传引用的概念,这也象《Practical Java》中所说的那样,Java中只有传值。这句话理解起来需要费一定的周折。
熟悉C的程序员都用过指针,对指针可谓爱之深恨之切。指针是指向一块内存地址的内存数据(有些拗口),也就是说指针本身是一个占用4字节内存的int(32 位系统内),而这个int值恰恰又是另一块内存的地址。比如”hello”这个字串,存放在@0x0000F000这个地址到@0x0000F005这段内存区域内(包括0x00的结束字节)。而在@0x0000FFF0到@0x0000FFF03这四个字节内存放着一个int,这个int的值是 @0x0000F000。这样就形成了一个指向”hello”字串的指针。
所谓传引用的说法是为了更好的讲解调用方式。基于上面对指针的理解,我们不难看出,指针其实也是一个int值,所谓传引用,我们是复制了复制了指针的int值进行传递。为了便于理解,我们可以姑且把指针看作一种数据类型,透明化指针的int特性,从而提出传引用的概念。
重申一遍:Java中只有传值。
1所谓传值和传引用
传值和传引用的问题一直是Java里争论的话题。与C++不同的,Java里面没有指针的概念,Java的设计者巧妙的对指针的操作进行了管理。事实上,在懂C++的Java程序员眼中,Java到处都是精美绝伦的指针。
下面举个简单的例子,说明什么是传值,什么是传引用。
所谓传值和传引用
传值和传引用的问题一直是Java里争论的话题。与C++不同的,Java里面没有指针的概念,Java的设计者巧妙的对指针的操作进行了管理。事实上,在懂C++的Java程序员眼中,Java到处都是精美绝伦的指针。
下面举个简单的例子,说明什么是传值,什么是传引用。
//例1 void method1(){ int x=0; this.change(x); System.out.println(x); } void change(int i){ i=1; }
很显然的,在mothod1中执行了change(x)后,x的值并不会因为change方法中将输入参数赋值为1而变成1,也就是说在执行change(x)后,x的值z依然是0。这是因为x传递给change(int i)的是值。这就是最简单的传值。
同样的,进行一点简单的变化。
//例2 void method1(){ StringBuffer x=new StringBuffer("Hello"); this.change(x); System.out.println(x); } void change(StringBuffer i){ i.append(" world!"); }
看起来没什么变化,但是这次mothed1中执行了change (x)后,x的值不再是”Hello”了,而是变成了”Hello world!”。这是因为x传递给change(i)的是x的引用。这是最经典的传引用。
似乎有些奇怪了,两段程序没有特别的不同,可是为什么一个传的是值而另一个传的是引用呢?……
2非要搞清楚传值还是传引用的问题吗?
搞清楚这自然是有必要的,不然我也不需要写这么多了,不过的确没有到”非要”的地步。
首先,如果我们不太关心什么是传值什么是传引用,我们一样能写出漂亮的代码,但是这些代码在运行过程中可能会存在着极大的隐患。全局变量是让大家深恶痛绝(又难以割舍)的东西,原因就是使用全局变量要特别注意数据的保护。如果在多线程的程序里使用全局变量简直就等于跟自己过不去。不了解传值和传引用的问题,跟使用全局变量不考虑数据保护的罪过是不相上下下的,甚至有时候比它还要糟。你会莫名其妙,为什么我的返回参数没有起作用,为什么我传进去的参数变成了这样……?
一个例子:
//例3 void mothed1(){ int x=0; int y=1; switchValue(x,y); System.out.println("x="+x); System.out.println("y="+y); } void switchValue(int a,int b){ int c=a; a=b; b=c; }
上面是一个交换a,b值的函数,看起来似乎蛮正确的,但是这个函数永远也不会完成你想要的工作。
还有一个例子:
//例4 StringBuffer a=new StringBuffer("I am a "); StringBuffer b=a; a.append("after append"); a=b; System.out.println("a="+a);
3类型和类
Java 提出的思想,在Java里面任何东西都是类。但是Java里面同时还有简单数据类型int,byte,char,boolean,与这些数据类型相对应的类是Integer,Byte,Character,Boolean,这样做依然不会破坏Java关于任何东西都是类的提法。这里提到数据类型和类似乎和我们要说的传值和传引用的问题无关,但这是我们分辨传值和传引用的基础。
4试图分辨传值还是传引用
为什么是”试图分辨”呢?很简单,传值和传引用的问题无处不在,但是似乎还没有人能正统的给出标准,怎样的就是值拷贝调用,怎样的就是引用调用。面对这个问题,我们更多的应该是来自平时积累对Java的理解。
回过头来,我们分析一下上面的几个例子:
先看例1,即使你不明白为什么,但是你应该知道这样做肯定不会改变x的值。为了方便说明,我们给例子都加上行号。
//例1 1 void method1(){ 2 int x=0; 3 this.change(x); 4 } 5 6 void change(int i){ 7 i=7; 8}
让我们从内存的存储方式看一下x和I之间到底是什么关系。
在执行到第2行的时候,变量x指向一个存放着int 0的内存地址。
变量x---->[存放值0] 执行第3行调用change(x)方法的时候,内存中是这样的情形:x把自己值在内存中复制一份,然后变量i指向这个被复制出来的0。 变量x---->[存放值0] ↓进行了一次值复制 变量x---->[存放值0] 这时候再执行到第7行的时候,变量i的被赋值为7,而这一步的操作已经跟x没有任何关系了。 变量x---->[存放值0] 变量x---->[存放值7] 说到这里应该已经理解为什么change(x)不能改变x的值了吧?因为这个例子是传值的。
那么,试着分析一下为什么例三中的switchValue()方法不能完成变量值交换的工作?
再看例2。
//例2 1void method1(){ 2 StringBuffer x=new StringBuffer("Hello"); 3 this.change(x); 4} 5 6void change(StringBuffer i){ 7 i.append(" world!"); 8}
例2似乎和例1从代码上看不出什么差别,但是执行结果却是change(x)能改变x的值。依然才从内存的存储角度来看看例2的蹊跷在哪里。
在执行到第2行时候,同例1一样,x指向一个存放”Hello”的内存空间。
变量x---->[存放值"Hello"] 接下来执行第三行change(x),注意,这里就与例1有了本质的不同:调用change(x)时,变量i也指向了x指向的内存空间,而不是指向x的一个拷贝。 变量x \ -->[存放值"Hello"] 变量x / 于是,第7行对i调用append方法,改变i指向的内存空间的值,x的值也就随之改变了。 变量x \ -->[追加为"Hello World!"] 变量x / 为什么x值能改变呢?因为这个例子是传引用的。
这几个例子是明白了,可是很多人会开始有另一个疑问了:这样看来,到底什么时候是传的值什么时候是传得引用呢?于是,我们前面讲到的类型和类在这里就派上了用场:对于参数传递,如果是简单数据类型,那么它传递的是值拷贝,对于类的实例它传递的是类的引用。需要注意的是,这条规则只适用于参数传递。为什么这么说呢?我们看看这样一个例子:
//例5 String str="abcdefghijk"; str.replaceAll("b","B");
这两句执行后,str的内容依然是”abcdefghijk”,但是我们明明是对str操作的,为什么是这样的呢?因为str的值究竟会不会被改变完全取决于replaceAll这个方法是怎么实现的。类似的,有这样一个例子:
//例6 void method1() { StringBuffer x = new StringBuffer("Hello"); change1(x); System.out.println(x); } void method2() { StringBuffer x = new StringBuffer("Hello"); change2(x); System.out.println(x); } void change1(StringBuffer sb) { sb.append(" world!"); } void change2(StringBuffer sb) { sb = new StringBuffer("hi"); sb.append(" world!"); }
调用method1(),屏幕打印结果为:”Hello world!”
调用method2(),我们认为结果应该是”hi world”,因为sb传进来的是引用。可是实际执行的结果是”Hello”!
难道change2()又变成传值了?!其实change1()和change2()的确都是通过参数传入引用,但是在方法内部因为处理方法的不同而使结果大相径庭。我们还是从内存的角度分析:
执行method1()和change1()不用再多说了,上面的例子已经讲解过,这里我们分析一下method2()和change2()。
程序执行到第8行,x指向一个存放着”Hello”的内存空间。
变量x---->[存放值"Hello"] 第9行调用change2,将sb指向x指向的内存空间,也就是传入x的引用。 变量x \ -->[存放值"Hello"] 变量x / 到这里为止还没有什么异样,接下来执行18行,这里就出现了类似传入值拷贝的变化:new 方法并没有改变sb指向内存的内容,而是在内从中开辟了一块新的空间存放串"hi",同时sb指向了这块空间。 变量x---->[存放值"Hello"] ×原有的引用被切断 变量x---->[另一块存放"hi"的空间] 接下来再对sb进行append已经和x没有任何关系了。
所以,还有一条不成规则的规则:对于函数调用,最终效果是什么完全看函数内部的实现。比较标准的做法是如果会改变引用的内容,则使用void作为方法返回值,而不会改变引用内容的则在返回值中返回新的值。
虽然已经说了这么多,但是感觉传值还是传引用的问题依然没有完全说清楚。因为这个问题本身就是很难归纳总结的问题,所以更多的理解要靠平时的积累和形成。下面几个例子,给大家尝试进行分析。
//例7,打印结果是什么? public static void main(String[] args) { int a; int b; StringBuffer c; StringBuffer d; a = 0; b = a; c = new StringBuffer("This is c"); d = c; a = 2; c.append("!!"); System.out.println("a=" + a); System.out.println("b=" + b); System.out.println("c=" + c); System.out.println("d=" + d); }
//例8,打印结果是什么? public class Test{ public static void main(String[] args) { StringBuffer sb = new StringBuffer("Hello "); System.out.println("Before change, sb = " + sb); changeData(sb); System.out.println("After changeData(n), sb = " + sb); } public static void changeData(StringBuffer strBuf) { StringBuffer sb2 = new StringBuffer("Hi "); strBuf = sb2; sb2.append("World!"); } }
相关文章推荐
- java notepad++
- 转!!java线程状态
- java发送邮件(2)
- SpringMVC + Spring 3.2.14 + Hibernate 3.6.10
- SpringMVC 文件上传配置,多文件上传,使用的MultipartFile
- JNI——Java调用DLL
- java基础知识整理:
- java平台的提高性能的几点建议
- 黑马程序员——struts2学习笔记一
- win7搭建android开发环境——离线一体化配置Eclipse+ADT+SDK
- 如何在spring mvc中上传图片并显示出来
- java 特种兵笔记:论道 unilay
- 设计模式-单例模式(饿汉式及懒汉式的Java实现)
- 开涛SpringMVC笔记
- 【JAVA】浅谈java枚举类
- Java 打开文件夹
- java Date获取 年月日时分秒
- java培训 2015-08-3 面向对象 类
- Java多线程同步的方法
- java多线程概述