实参:如果声明方法时包含来了形参声明,则调用方法时必须给这些形参指定参数值,调用方法时传给形参的参数值也被称为实参。

Java的实参值是如何传入方法?这是由Java方法的参数传递机制来控制的,Java里方法的参数传递方式只有一种:值传递。所谓值传递,就是将实际参数的副本(复制品)传入方法内,而参数本身不会收到任何影响。

一、参数类型是原始类型的值传递

下面通过一个程序来演练
参数类型是原始类型的值传递的效果:

  1. public class ParamTransferTest {
  2.  
  3. public static void swap(int a,int b){
  4. //下面三行代码实现a、b变量的值交换
  5. //定义一个临时变量来保存a变量的值
  6. int temp=a;
  7. //把b的值赋给a
  8. a=b;
  9. //把临时变量temp的值赋给a
  10. b=temp;
  11. System.out.println("swap方法中,a的值是:"+a+", b的值是:"+b);
  12. }
  13.  
  14. public static void main(String[] args) {
  15. int a=6;
  16. int b=9;
  17. swap(a, b);
  18. System.out.println("交换结束后,变量a的值是:"+a+", 变量b的值是:"+b);
  19. }
  20.  
  21. }

程序结果为:

  1. swap方法中,a的值是:9, b的值是:6
  2. 交换结束后,变量a的值是:6, 变量b的值是:9

从上面运行结果来看,swap方法中的a和b的值是9、6,交换结束后,变量a和b的值依然是6、9。

从这个运行结果可以看出,main方法里的变量a和b,并不是swap方法中的a和b。

swap方法中的a和b只是main方法中的变量a和b的复制品。下面通过示意图来说明上面程序的执行过程。

Java程序总是从main方法开始执行,main方法开始定义了a、b两个局部变量,两个变量在内存中存储示意图如图1所示。

当程序执行swap方法是,系统进入swap方法,并将main方法中的a、b变量作为参数传入swap方法,传入swap方法的只是a、b的副本,而不是a、b本身,进入swap方法后系统产生了4个变量,这4个变量在内存中的存储示意图如图2所示。

 
   

在main方法中调用swap方法时,main方法还未结束。因此,系统分别为main方法和swap方法分配两块栈区,用于保存main方法和swap方法的局部变量。main方法中的a、b变量作为参数值传入swap方法,实际上是在swap方法栈区中重新产生两个变量a、b,并将main方法栈区中的a、b变量的值分别赋给swap方法栈区中的a、b参数(就是对swap方法的a、b形参进行初始化)。此时,系统存在两个a变量,两个b变量,只是存在于不同的方法栈区中而已。

程序在swap方法中交换a、b两个变量的值,实际上是对图2中灰色覆盖区域的a、b变量进行交换,交换结束后swap方法中输出a、b变量的值,看到a的值为9,b的值为6,此时内存中的存储示意图如图3所示。

对比图3和图2,两个示意图中main方法栈区的a、b值并未有任何改变,程序改变的只是swap方法栈区中的a、b值。

这就是值传递的实质:当系统开始执行方法时,系统为形参执行初始化,就是把实参变量的值赋给方法的形参变量,方法里操作并不是实际的参数变量。

二、参数类型是引用类型值传递

下面通过一个程序来演练 参数类型是引用类型的值传递的效果:

  1. class DataWrap {
  2. public int a;
  3. public int b;
  4. }
  5.  
  6. public class ReferenceTransferTest {
  7.  
  8. public static void swap(DataWrap dw) {
  9. // 下面三行代码实现dw对象的a、b两个Field交换
  10. // 定义一个临时变量来保存dw对象a Field的值
  11. int temp = dw.a;
  12. // 把dw对象b Field的值赋给a Field
  13. dw.a = dw.b;
  14. // 把临时变量temp的值赋给dw对象b Field
  15. dw.b = temp;
  16. System.out.println("swap方法中,a Field的值是:" + dw.a + ", b Field的值是:"
  17. + dw.b);
  18. }
  19.  
  20. public static void main(String[] args) {
  21. DataWrap dw = new DataWrap();
  22. dw.a = 6;
  23. dw.b = 9;
  24. swap(dw);
  25. System.out.println("交换结束后,a Field的值是:" + dw.a + ", b Field的值是:" + dw.b);
  26. }
  27.  
  28. }

程序结果为:

  1. swap方法中,a Field的值是:9, b Field的值是:6
  2. 交换结束后,a Field的值是:9, b Field的值是:6

从上面运行结果来看,在swap方法里,a、b两个Field值被交换成功。不仅如此,main方法里swap方法执行结束后,a、b两个Field值也被交换了。这很容易造成一个错觉:调用swap方法时,传入swap方法的就是dw对象本身,而不是它的复制品。但是这仅仅是一种错觉,下面还是结合示意图来说明程序的执行过程。

程序从main方法开始执行,main方法开始创建了一个DataWrap对象,并定义了一个dw引用变量来指向DataWrap对象,这是一个与基本类型不同的地方。创建一个对象时,系统内存中有两个东西:堆内存中保存对象本身,栈内存中保存了引用该对象的引用变量。接着程序通过引用来操作DataWrap对象,把该对象的a、b两个Field分别赋值给6、9。此时系统内存中的存储示意图如图4所示。

接下来,main方法中开始调用swap方法,main方法并未结束,系统会分别开辟出main和swap两个栈区,用于存放main和swap方法的局部变量。调用swap方法时,dw变量作为实参传入swap方法,同样采用值传递方式:把main方法里的dw变量的值赋给swap方法里的dw形参,从而完成swap方法的dw形参的初始化。值得指出的是:main方法中的dw是一个引用(也就是一个指针),它保存了DataWrap对象的地址值,当把dw的值赋给swap方法的dw形参后,即让swap方法的dw形参也保存了这个地址值,即也会引用到堆内存中的DataWrap对象。图5显示了dw传入swap方法后的存储示意图。

从图5来看,这种参数传递方式是不折不扣的值传递方式,系统一样复制了dw的副本传入swap方法,但关键在于dw只是一个引用变量,所以系统复制了dw变量,但并未复制DataWrap对象 。

当程序在swap方法中操作dw形参时,由于dw只是一个引用变量,故实际操作的还是堆内存中的DataWrap对象。此时,不管操作main方法里的dw变量,还是操作swap方法里的dw变量,其实都是操作它所引用的DataWrap对象,它们操作的是同一个对象。因此,当swap方法中交换dw参数所引用DataWrap对象的a、b两个Field值后,我们看到main方法中的dw变量所引用DataWrap对象的a、b两个Field值也被交换了。

为了更好地证明main方法中的dw和swap方法中的dw是两个变量,我们在swap方法的最好一行增加如下代码:dw=null;

  1. public static void swap(DataWrap dw) {
  2. // 下面三行代码实现dw对象的a、b两个Field交换
  3. // 定义一个临时变量来保存dw对象a Field的值
  4. int temp = dw.a;
  5. // 把dw对象b Field的值赋给a Field
  6. dw.a = dw.b;
  7. // 把临时变量temp的值赋给dw对象b Field
  8. dw.b = temp;
  9. System.out.println("swap方法中,a Field的值是:" + dw.a + ", b Field的值是:"
  10. + dw.b);
  11. //把dw直接赋值为null,让它不再指向任何有效地址
  12. dw=null;
  13. }

执行上面修改后的代码结果是swap方法中的dw变量不再指向任何有效内存,程序其他地方不做任何修改。main方法调用swap方法后,再次访问dw变量a、b两个Field,依然可以输出9、6。可见,main方法中的dw变量没有收到任何影响。实际上,当swap方法中增加dw=null;代码后,内存中的存储示意图如图6所示。

从图6来看,把swap方法中的dw赋值为null后,swap方法中失去了DataWrap对象的引用,不可再访问堆内存中的DataWrap对象。但main方法中的dw变量不受任何影响,依然引用DataWrap对象,所以依然可以输出DataWrap对象的a、b Field值。

三、总结


Java 编程语言只有值传递参数。当一个对象实例作为一个参数被传递到方法中时,参数的值就是该对象的引用一个副本。指向同一个对象,对象的内容可以在被调用的方法中改变,但对象的引用(不是引用的副本)是永远不会改变的。

  Java参数,不管是原始类型还是引用类型,传递的都是副本(有另外一种说法是传值,但是说传副本更好理解吧,传值通常是相对传址而言)。

  如果参数类型是原始类型,那么传过来的就是这个参数的一个副本,也就是这个原始参数的值,这个跟之前所谈的传值是一样的。如果在函数中改变了副本的值不会改变原始的值.
  如果参数类型是引用类型,那么传过来的就是这个引用参数的副本,这个副本存放的是参数的地址。如果在函数中没有改变这个副本的地址,而是改变了地址中的值,那么在函数内的改变会影响到传入的参数。如果在函数中改变了副本的地址,如new一个,那么副本就指向了一个新的地址,此时传入的参数还是指向原来的地址,所以不会改变参数的值。

                        

                 ========以上内容出处为李刚老师的《疯狂Java讲义》=======




==================================================================================================

  作者:欧阳鹏  欢迎转载,与人分享是进步的源泉!

  转载请保留原文地址:http://blog.csdn.net/ouyang_peng

==================================================================================================


我的Java开发学习之旅------>Java语言中方法的参数传递机制的更多相关文章

  1. 我的Java开发学习之旅------>Java 格式化类(java.util.Formatter)基本用法

    本文参考: http://docs.oracle.com/javase/1.5.0/docs/api/java/util/Formatter.html http://www.blogjava.net/ ...

  2. 我的Java开发学习之旅------>Java使用Fork/Join框架来并行执行任务

    现代的计算机已经向多CPU方向发展,即使是普通的PC,甚至现在的智能手机.多核处理器已被广泛应用.在未来,处理器的核心数将会发展的越来越多. 虽然硬件上的多核CPU已经十分成熟,但是很多应用程序并未这 ...

  3. 我的Java开发学习之旅------>Java NIO 报java.nio.charset.MalformedInputException: Input length = 1异常

    今天在使用Java NIO的Channel和Buffer进行文件操作时候,报了java.nio.charset.MalformedInputException: Input length = 1异常, ...

  4. 我的Java开发学习之旅------>Java使用ObjectOutputStream和ObjectInputStream序列号对象报java.io.EOFException异常的解决方法

    今天用ObjectOutputStream和ObjectInputStream进行对象序列化话操作的时候,报了java.io.EOFException异常. 异常代码如下: java.io.EOFEx ...

  5. 我的Java开发学习之旅------>Java利用Comparator接口对多个排序条件进行处理

    一需求 二实现Comparator接口 三验证排序结果 验证第一条件首先按级别排序级别最高的排在前面 验证第二条如果级别相等那么按工资排序工资高的排在前面 验证第三条如果工资相当则按入职年数排序入职时 ...

  6. 我的Java开发学习之旅------>求字符串中出现次数最多的字符串以及出现的次数

    金山公司面试题:一个字符串中可能包含a~z中的多个字符,如有重复,如String data="aavzcadfdsfsdhshgWasdfasdf",求出现次数最多的那个字母及次数 ...

  7. 我的Java开发学习之旅------>Java String对象作为参数传递的问题解惑

    又是一道面试题,来测试你的Java基础是否牢固. 题目:以下代码的运行结果是? public class TestValue { public static void test(String str) ...

  8. 我的Java开发学习之旅------>Java经典排序算法之归并排序

    一.归并排序 归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用.将已有序的子序列合并,得到完全有序的序列:即先使每个子序列 ...

  9. 我的Java开发学习之旅------>Java经典排序算法之快速排序

    一.算法思想     快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序.它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod).(1) 分治法的 ...

随机推荐

  1. Wildcard Matching - LeetCode

    Implement wildcard pattern matching with support for '?' and '*'. '?' Matches any single character. ...

  2. 寒假week1---二分查找(二分枚举)

    寒假week1---二分查找(二分枚举)1.适用条件:要查找(枚举)的集合有序 && 查找(枚举)的“条件”具有单调性2.什么是“条件”:example: 1.给定一个有序数组,从中查 ...

  3. delphi 按位运算 not and or xor shl shr

    delphi 按位运算 not and or xor shl shr unit Unit1;   interface   uses   Windows, Messages, SysUtils, Var ...

  4. win8.1安装VMware Error:This product may not be installed on a comuputer that has Microsoft HyperV installed

    之前用的win7,安装虚机没遇到这问题,换了win8.1后,再安装虚机,就会出现下面的错误.没办法,还是记录一下吧. Error:This product may not be installed o ...

  5. ASIHTTPRequest 类库在iOS 7.0中,会有一些报错警告,需要稍作修改

    1. if ([inputStream streamStatus] == NSStreamEventErrorOccurred) { 修改成: if ([inputStream streamStatu ...

  6. json字符串调整

    碰到比较长的json字符串,不知道哪里出错时,可以找一个正确的json字符串,慢慢把它调整到需要的形式,而不是去分析,字符串太长,一直看,效率太慢,容易看花眼.

  7. nginx 启动,重启,添加开机启动等相关命令

    nginx -t 测试 配置文件是否正确,同时可以查看配置文件路径 nginx -c /usr/local/nginx/conf/nginx.conf   启动nginx ps -ef|grep ng ...

  8. C#是唯一能挑战Java的编程语言?

    几乎所有新近成长的Visual Studio代码开发人员都选择使用C#,而不是VB.NET或C++,这也使得C#已经成长为微软的第一大语言.根据本月的Tiobe编程语言排行榜,C#再次取得了突破性进展 ...

  9. IDEA破解 2017 IDEA license server 激活(可用)

    进入ide主页面,help-register-license server,然后输入 http://idea.iteblog.com/key.PHP(注意:php要小写)即可~

  10. RR调度(Round-robin scheduling)简单介绍

    在RR调度策略下,一个线程会一直运行.直到: 自愿放弃控制权 被更高优先级的线程抢占 时间片用完 例如以下图所看到的,A在用完自己的时间片后,将CPU运行权让给线程B.于是A离开Read队列,而B进入 ...