当前位置: 首页 > news >正文

【Java基础】12、java中方法的参数传递机制

问:当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递? 

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

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

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

无论是什么语言,要讨论参数传递方式,就得从内存模型说起,主要是我个人觉得从内存模型来说参数传递更为直观一些。闲言少叙,下面我们就通过内存模型的方式来讨论一下Java中的参数传递。

这里的内存模型涉及到两种类型的内存:栈内存(stack)和堆内存(heap)。基本类型作为参数传递时,传递的是这个值的拷贝。无论你怎么改变这个拷贝,原值是不会改变的。看下边的一段代码,然后结合内存模型来说明问题:

1
2
3
4
5
6
7
8
9
10
11
12
public  class  ParameterTransfer {
      public  static  void  main(String[] args) {
         int  num =  30 ;
         System.out.println( "调用add方法前num="  + num);
         add(num);
         System.out.println( "调用add方法后num="  + num);
      }
  
      public  static  void  add( int  param) {
           param =  100 ;
      }
}                

这段代码运行的结果如下:

1
2
调用add方法前num= 30
调用add方法后num= 30

程序运行的结果也说明这一点,无论你在add()方法中怎么改变参数param的值,原值num都不会改变。

  下边通过内存模型来分析一下。

  当执行了int num = 30;这句代码后,程序在栈内存中开辟了一块地址为AD8500的内存,里边放的值是30,内存模型如下图:

                    
  执行到add()方法时,程序在栈内存中又开辟了一块地址为AD8600的内存,将num的值30传递进来,此时这块内存里边放的值是30,执行param = 100;后,AD8600中的值变成了100。内存模型如下图:   
                 

  地址AD8600中用于存放param的值,和存放num的内存没有任何关系,无论你怎么改变param的值,实际改变的是地址为AD8600的内存中的值,而AD8500中的值并未改变,所以num的值也就没有改变。
   以上是基本类型参数的传递方式,下来我们讨论一下对象作为参数传递的方式。

  先看下边的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
public  class  ParameterTransfer {
      public  static  void  main(String[] args) {
           String[] array =  new  String[] { "huixin" };
           System.out.println( "调用reset方法前array中的第0个元素的值是:"  + array[ 0 ]);
           reset(array);
           System.out.println( "调用reset方法后array中的第0个元素的值是:"  + array[ 0 ]);
      }
  
      public  static  void  reset(String[] param) {
           param[ 0 ] =  "hello, world!" ;
      }
}   

运行的结果如下:

1
2
调用reset方法前array中的第 0 个元素的值是:huixin
调用reset方法后array中的第 0 个元素的值是:hello, world!

当对象作为参数传递时,传递的是对象的引用,也就是对象的地址。下边用内存模型图来说明。

       

  当程序执行了String[] array = new String[] {"huixin"}后,程序在栈内存中开辟了一块地址编号为AD9500内存空间,用于存放array[0]的引用地址,里边放的值是堆内存中的一个地址,示例中的值为BE2500,可以理解为有一个指针指向了堆内存中的编号为BE2500的地址。堆内存中编号为BE2500的这个地址中存放的才是array[0]的值:huixin。

  当程序进入reset方法后,将array的值,也就是对象的引用BE2500传了进来。这时,程序在栈内存中又开辟了一块编号为AD9600的内存空间,里边放的值是传递过来的值,即AD9600。可以理解为栈内存中的编号为AD9600的内存中有一个指针,也指向了堆内存中编号为BE2500的内存地址,如图所示:

       

  这样一来,栈内存AD9500和AD9600(即array[0]和param的值)都指向了编号为BE2500的堆内存。

  在reset方法中将param的值修改为hello, world!后,内存模型如下图所示:

       

  改变对象param的值实际上是改变param这个栈内存所指向的堆内存中的值。param这个对象在栈内存中的地址是AD9600,里边存放的值是BE2500,所以堆内存BE2500中的值就变成了hello,world!。程序放回main方法之后,堆内存BE2500中的值仍然为hello,world!,main方法中array[0]的值时,从栈内存中找到array[0]的值是BE2500,然后去堆内存中找编号为BE2500的内存,里边的值是hello,world!。所以main方法中打印出来的值就变成了hello,world!

  

小结:

  无论是基本类型作为参数传递,还是对象作为参数传递,实际上传递的都是值,只是值的的形式不用而已。第一个示例中用基本类型作为参数传递时,将栈内存中的值30传递到了add方法中。第二个示例中用对象作为参数传递时,将栈内存中的值BE2500传递到了reset方法中。当用对象作为参数传递时,真正的值是放在堆内存中的,传递的是栈内存中的值,而栈内存中存放的是堆内存的地址,所以传递的就是堆内存的地址。这就是它们的区别。

补充一下,在Java中,String是一个引用类型,但是在作为参数传递的时候表现出来的却是基本类型的特性,即在方法中改变了String类型的变量的值后,不会影响方法外的String变量的值。关于这个问题,可以参考如下两个地址:

http://freej.blog.51cto.com/235241/168676

http://dryr.blog.163.com/blog/static/58211013200802393317600/

我觉得是这两篇文章中提到的两个原因导致的,一个是String实际上操作的是char[],可以理解为String是char[]的包装类。二是给String变量重新赋值后,实际上没有改变这个变量的值,而是重新new了一个String对象,改变了新对象的值,所以原来的String变量的值并没有改变。

转载于:https://www.cnblogs.com/wangzhongqiu/p/6602934.html

相关文章:

  • OpenNebula学习第四节之磁盘镜像的制作
  • 转:Spring Boot中使用AOP统一处理Web请求日志
  • 《零基础入门学习Python》学习过程笔记【38类的继承】
  • 制作毛玻璃效果
  • 设计模式——简单工厂模式
  • 吐血分享:QQ群霸屏技术教程(接单篇)
  • js全局变量,局部变量
  • 2017百度之星资格赛 1003 度度熊与邪恶大魔王 背包DP
  • 8086汇编之 CALL 和 RET指令
  • c# 多线程编程中AutoResetEvent和ManualResetEvent
  • 【Python】 配置文件相对路径软件自动执行的工作目录
  • [SDUT](3361) 数据结构实验之图论四:迷宫探索 ---DFS(图)
  • proxy汇总-1
  • 使用for循环输出九九乘法表
  • ffmpeg学习(二) 通过rtsp获取H264裸流并保存到mp4文件
  • Brief introduction of how to 'Call, Apply and Bind'
  • CSS中外联样式表代表的含义
  • ECS应用管理最佳实践
  • ES2017异步函数现已正式可用
  • Linux学习笔记6-使用fdisk进行磁盘管理
  • Mac转Windows的拯救指南
  • PHP面试之三:MySQL数据库
  • Promise面试题2实现异步串行执行
  • QQ浏览器x5内核的兼容性问题
  • Sass Day-01
  • vuex 笔记整理
  • 阿里云ubuntu14.04 Nginx反向代理Nodejs
  • 看完九篇字体系列的文章,你还觉得我是在说字体?
  • 爬虫模拟登陆 SegmentFault
  • 前端路由实现-history
  • 数据结构java版之冒泡排序及优化
  • 数组的操作
  • 项目管理碎碎念系列之一:干系人管理
  • 学习HTTP相关知识笔记
  • 源码之下无秘密 ── 做最好的 Netty 源码分析教程
  • “十年磨一剑”--有赞的HBase平台实践和应用之路 ...
  • k8s使用glusterfs实现动态持久化存储
  • Linux权限管理(week1_day5)--技术流ken
  • ​LeetCode解法汇总2182. 构造限制重复的字符串
  • ​比特币大跌的 2 个原因
  • #pragma pack(1)
  • %3cli%3e连接html页面,html+canvas实现屏幕截取
  • (k8s中)docker netty OOM问题记录
  • (MATLAB)第五章-矩阵运算
  • (solr系列:一)使用tomcat部署solr服务
  • (分享)自己整理的一些简单awk实用语句
  • (附源码)spring boot建达集团公司平台 毕业设计 141538
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理第3章 信息系统治理(一)
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • (转)Groupon前传:从10个月的失败作品修改,1个月找到成功
  • (转)一些感悟
  • ****** 二十三 ******、软设笔记【数据库】-数据操作-常用关系操作、关系运算
  • ./configure、make、make install 命令
  • .net core 6 redis操作类
  • .NET Framework 和 .NET Core 在默认情况下垃圾回收(GC)机制的不同(局部变量部分)