小方法大门道

小瓜瓜作为一个Java初学者,今天跟我说她想通过一个Java方法,将外部变量通过参数传递到方法中去,进行逻辑处理,方法执行完毕之后,再对修改过的变量进行判断处理,代码如下所示。

  1. public class MethodParamsPassValue {
  2.  
  3. public static void doErrorHandle() {
  4. boolean a = false;
  5. int b = 5;
  6. passBaseValue(a, b);
  7. if (a == true || b == 10) {
  8. System.out.println("Execute Something");
  9. } else {
  10. System.out.println("param result wrong");
  11. }
  12. }
  13.  
  14. public static void passBaseValue(boolean flg, int num) {
  15. flg = true;
  16. num = 10;
  17. }
  18.  
  19. public static void main(String[] args) {
  20. doErrorHandle();
  21. }
  22. }

上述代码是有问题的,布尔变量a和整型变量b在方法操作之后,它们的值并没有发生变化,小瓜瓜事与愿违。

究其原因

在Java方法中参数列表有两种类型的参数,基本类型和引用类型。

基本类型:值存放在局部变量表中,无论如何修改只会修改当前栈帧的值,方法执行结束对方法外不会做任何改变;此时需要改变外层的变量,必须返回主动赋值。

引用数据类型:指针存放在局部变量表中,调用方法的时候,副本引用压栈,赋值仅改变副本的引用。但是如果通过操作副本引用的值,修改了引用地址的对象,此时方法以外的引用此地址对象当然被修改。(两个引用,同一个地址,任何修改行为2个引用同时生效)。

这两种类型都是将外面的参数变量拷贝一份到局部变量中,基本类型为值拷贝,引用类型就是将引用地址拷贝一份。

方法参数为基本类型的值传递

  1. public class MethodParamsPassValue {
  2.  
  3. public static void passBaseValue(boolean flg, int num) {
  4. flg = true;
  5. num = 10;
  6. }
  7.  
  8. public static void main(String[] args) {
  9. boolean a = false;
  10. int b = 5;
  11. System.out.println("a : " + a + " b : " + b);
  12. passBaseValue(a, b);
  13. System.out.println("a : " + a + " b : " + b);
  14. }
  15. }

返回结果

  1. a : false b : 5
  2. a : false b : 5

1. 方法参数flg被初始化为外部变量a的拷贝,值为false。参数num被初始化为外部变量b的拷贝,值为5。

2. 执行方法逻辑,方法中的局部变量flg被改变为true,局部变量flg被改变为10。

3.方法执行完毕,不再局部变量不再被使用到,等待被GC回收。

结论:当方法参数为基本类型时,是将外部变量值拷贝到局部变量中而进行逻辑处理的,故方法是不能修改原基本变量的。

方法参数为包装类型的引用传递

  1. public class MethodParamsPassValue {
  2.  
  3. public static void passReferenceValue(Boolean flg, Integer num) {
  4. flg = true;
  5. num = 10;
  6. }
  7.  
  8. public static void main(String[] args) {
  9. Boolean a = false;
  10. Integer b = 5;
  11. System.out.println("a : " + a + " b : " + b);
  12. passReferenceValue(a, b);
  13. System.out.println("a : " + a + " b : " + b);
  14. }
  15. }

结果为  

  1. a : false b : 5
  2. a : false b : 5

当传入参数为包装类型时,为对象的引用地址拷贝。那么既然是引用拷贝为什么还是没有更改原来的包装类型的变量值呢?

这是因为Java中的自动装箱机制,当在方法中执行 flg = true 时,实际在编译后执行的是 flg = Boolean.valueOf(true),即又会产生一个新的Boolean对象。同理Integer num也是如此。

方法参数为类的对象引用时

  1. public class ParamObject {
  2.  
  3. private boolean flg;
  4.  
  5. private int num;
  6.  
  7. public ParamObject(boolean flg, int num) {
  8. this.flg = flg;
  9. this.num = num;
  10. }
  11.  
  12. public boolean isFlg() {
  13. return flg;
  14. }
  15.  
  16. public void setFlg(boolean flg) {
  17. this.flg = flg;
  18. }
  19.  
  20. public int getNum() {
  21. return num;
  22. }
  23.  
  24. public void setNum(int num) {
  25. this.num = num;
  26. }
  27.  
  28. @Override
  29. public String toString() {
  30. return "ParamObject{" +
  31. "flg=" + flg +
  32. ", num=" + num +
  33. '}';
  34. }
  35. }
  1. public class MethodParamsPassValue {
  2.  
  3. public static void passObjectValue(ParamObject paramObject) {
  4. paramObject.setFlg(true);
  5. paramObject.setNum(10);
  6. }
  7.  
  8. public static void main(String[] args) {
  9. ParamObject a = new ParamObject(false, 5);
  10. System.out.println(a);
  11. passObjectValue(a);
  12. System.out.println(a);
  13. }
  14. }  

结果为

  1. ParamObject{flg=false, num=5}
  2. ParamObject{flg=true, num=10}

结论:对于引用类型的方法参数,会将外部变量的引用地址,复制一份到方法的局部变量中,两个地址指向同一个对象。所以如果通过操作副本引用的值,修改了引用地址的对象,此时方法以外的引用此地址对象也会被修改。(两个引用,同一个地址,任何修改行为2个引用同时生效)。

脑筋急转弯之'交换两个对象'

  1. public class MethodParamsPassValue {
  2.  
  3. public static void swapObjectReference(ParamObject object1, ParamObject object2) {
  4. ParamObject temp = object1;
  5. object1 = object2;
  6. object2 = temp;
  7. }
  8.  
  9. public static void main(String[] args) {
  10. ParamObject a = new ParamObject(true, 1);
  11. ParamObject b = new ParamObject(false, 2);
  12. System.out.println("a : " + a + " b : " + b);
  13. swapObjectReference(a, b);
  14. System.out.println("a : " + a + " b : " + b);
  15. }
  16. }  

结果为

  1. a : ParamObject{flg=true, num=1} b : ParamObject{flg=false, num=2}
  2. a : ParamObject{flg=true, num=1} b : ParamObject{flg=false, num=2}

有了上面的知识之后,我们会发现这个方法中的引用地址交换,只不过是一个把戏而已,只是对方法中的两个局部变量的对象引用值进行了交换,不会对原变量引用产生任何影响的。

一个方法返回两个返回值

Java方法中只能Return一个返回值,那么如何在一个方法中返回两个或者多个返回值呢?我们可以通过使用泛型来定义一个二元组来达到我们的目的。

  1. public class TwoTuple<A, B> {
  2.  
  3. public final A first;
  4.  
  5. public final B second;
  6.  
  7. public TwoTuple(A a, B b) {
  8. first = a;
  9. second = b;
  10. }
  11.  
  12. public String toString() {
  13. return "(" + first + ", " + second + ")";
  14. }
  15. }

  

  1. public class MethodParamsPassValue {
  2.  
  3. public static TwoTuple<Boolean, Integer> returnTwoResult(Boolean flg, Integer num) {
  4. flg = true;
  5. num = 10;
  6. return new TwoTuple<>(flg, num);
  7. }
  8.  
  9. public static void main(String[] args) {
  10. TwoTuple<Boolean,Integer> result = returnTwoResult(false,5);
  11. System.out.println("first : " + result.first + ", second : " + result.second);
  12. }
  13. }

  

完整代码

  1. package com.lingyejun.authenticator;
  2.  
  3. public class MethodParamsPassValue {
  4.  
  5. public static void doErrorHandle() {
  6. boolean a = false;
  7. int b = 5;
  8. passBaseValue(a, b);
  9. if (a == true || b == 10) {
  10. System.out.println("Execute Something");
  11. } else {
  12. System.out.println("param result wrong");
  13. }
  14. }
  15.  
  16. /**
  17. * 基本类型,赋值运算=,会直接改变变量的值,原来的值被覆盖掉
  18. * 引用类型,复制运算=,会改变引用中所保存的地址,旧地址被覆盖掉,但原来的对象不会改变。
  19. *
  20. * @param flg
  21. * @param num
  22. */
  23. public static void passBaseValue(boolean flg, int num) {
  24. flg = true;
  25. num = 10;
  26. }
  27.  
  28. public static void passReferenceValue(Boolean flg, Integer num) {
  29. flg = true;
  30. num = 10;
  31. }
  32.  
  33. public static void passObjectValue(ParamObject paramObject) {
  34. paramObject.setFlg(true);
  35. paramObject.setNum(10);
  36. }
  37.  
  38. public static void swapObjectReference(ParamObject object1, ParamObject object2) {
  39. ParamObject temp = object1;
  40. object1 = object2;
  41. object2 = temp;
  42. }
  43.  
  44. public static TwoTuple<Boolean, Integer> returnTwoResult(Boolean flg, Integer num) {
  45. flg = true;
  46. num = 10;
  47. return new TwoTuple<>(flg, num);
  48. }
  49.  
  50. public static void main(String[] args) {
  51.  
  52. doErrorHandle();
  53.  
  54. System.out.println("============================");
  55.  
  56. boolean initFlg = false;
  57. int initNum = 5;
  58.  
  59. System.out.println("init flg : " + initFlg + " init num : " + initNum);
  60.  
  61. passBaseValue(initFlg, initNum);
  62.  
  63. System.out.println("init flg : " + initFlg + " init num : " + initNum);
  64.  
  65. System.out.println("============================");
  66.  
  67. Boolean referenceFlg = false;
  68. Integer referenceNum = 5;
  69.  
  70. System.out.println("reference flg : " + referenceFlg + " reference num : " + referenceNum);
  71.  
  72. passReferenceValue(referenceFlg, referenceNum);
  73.  
  74. System.out.println("reference flg : " + referenceFlg + " reference num : " + referenceNum);
  75.  
  76. System.out.println("============================");
  77.  
  78. ParamObject paramObject = new ParamObject(false, 5);
  79.  
  80. System.out.println(paramObject);
  81.  
  82. passObjectValue(paramObject);
  83.  
  84. System.out.println(paramObject);
  85.  
  86. System.out.println("============================");
  87.  
  88. ParamObject object1 = new ParamObject(true, 1);
  89. ParamObject object2 = new ParamObject(false, 2);
  90.  
  91. System.out.println("object1 : " + object1 + " object2 : " + object2);
  92.  
  93. swapObjectReference(object1, object2);
  94.  
  95. System.out.println("object1 : " + object1 + " object2 : " + object2);
  96.  
  97. System.out.println("============================");
  98.  
  99. TwoTuple<Boolean,Integer> result = returnTwoResult(false,5);
  100.  
  101. System.out.println("first : " + result.first + ", second : " + result.second);
  102. }
  103. }

  

参考文章:

https://blog.csdn.net/javazejian/article/details/51192130

https://blog.csdn.net/fenglllle/article/details/81389286

https://www.hollischuang.com/archives/2700

https://www.zhihu.com/question/31203609

辨析Java方法参数中的值传递和引用传递的更多相关文章

  1. java方法参数传递方式只有----值传递!

    在通常的说法中,方法参数的传递分为两种,值传递和引用传递,值传递是指将实际参数复制一份传递到方法中, 在方法中的改动将不会影响到实际参数本身,而引用传递则是指传递的是实际参数本身,在方法中的改动将会影 ...

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

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

  3. java中方法的参数传递机制(值传递还是引用传递)

    看到一个java面试题: 问:当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?  答:是值传递.Java 编程语言只有值传递参 ...

  4. Java中引用类型变量,对象,值类型,值传递,引用传递 区别与定义

    一.Java中什么叫做引用类型变量?引用:就是按内存地址查询       比如:String s = new String();这个其实是在栈内存里分配一块内存空间为s,在堆内存里new了一个Stri ...

  5. java中值传递和引用传递

    最近工作中使用到了值传递和引用传递,但是有点懵,现在看了下面的文章后清晰多了.一下是文章(网摘) 1:按值传递是什么 指的是在方法调用时,传递的参数是按值的拷贝传递.示例如下: public clas ...

  6. Java中的值传递和引用传递

    这几天一直再纠结这个问题,今天看了这篇文章有点思路了,这跟C++里函数参数为引用.指针还是有很大区别. 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里 ...

  7. Java基础学习笔记十二 类、抽象类、接口作为方法参数和返回值以及常用API

    不同修饰符使用细节 常用来修饰类.方法.变量的修饰符 public 权限修饰符,公共访问, 类,方法,成员变量 protected 权限修饰符,受保护访问, 方法,成员变量 默认什么也不写 也是一种权 ...

  8. java中的值传递和引用传递有什么区别呀?

    值传递: (形式参数类型是基本数据类型和String):方法调用时,实际参数把它的值传递给对应的形式参数,形式参数只是用实际参数的值初始化自己的存储单元内容,是两个不同的存储单元,所以方法执行中形式参 ...

  9. Java中的值传递与引用传递

    1.基本类型和引用类型在内存中的保存 Java中数据类型分为两大类,基本类型和对象类型.相应的,变量也有两种类型:基本类型和引用类型. 基本类型的变量保存原始值,即它代表的值就是数值本身: 而引用类型 ...

随机推荐

  1. TCP三次握手四次挥手介绍

    学过计算机网络的同学都知道TCP协议是计算机网络课程里面最复杂的协议之一,还没有通信就要搞个什么三次握手,断开还要什么四次分手,中间还要什么流量控制啦,拥塞控制,滑动窗口什么的,初学者看了就会头晕. ...

  2. 连续子数组的最大乘积及连续子数组的最大和(Java)

    1. 子数组的最大和 输入一个整形数组,数组里有正数也有负数.数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和.求所有子数组的和的最大值.例如数组:arr[]={1, 2, 3, -2, ...

  3. vue中常见的指令

    1,差值表达式{{}} <p >{{ msg }}</p> 2.v-cloak解决差值表达式闪烁的问题 <p v-cloak>{{ msg }}</p> ...

  4. 基于MUI框架+HTML5PLUS 开发 iOS和Android 应用程序(APP)

    目录 事前准备 创建项目 利用MUI写一个简单的页面 关于文件打包 事前准备 # 软件 HBuilder X Web开发IDE 下载地址:https://www.dcloud.io/hbuilderx ...

  5. koa2--nodemailer实现邮箱验证

    依赖包安装: /** * koa-bodyparser用于把formData数据解析到ctx.request.body * 通过ctx.request.body访问请求的参数 * koa-redis用 ...

  6. Android存储及getCacheDir()、getFilesDir()、getExternalFilesDir()、getExternalCacheDir()区别

    存储介绍 Android系统分为内部存储和外部存储,内部存储是手机系统自带的存储,一般空间都比较小,外部存储一般是SD卡的存储,空间一般都比较大,但不一定可用或者剩余空间可能不足.一般我们存储内容都会 ...

  7. element中日期时间插件(DateTimePicke) el-date 开始时间大于等于当前时间小于结束时间,结束时间大于开始时间且大于当前时间

    pickerOptions1: { disabledDate: time => { if (this.endTime) { return ( time.getTime() > new Da ...

  8. Docker搭建私用仓库

    搭建私有仓库 # 1.查找registry,官方的私用仓库镜像 docker search registry # 2.下载私有仓库镜像 docker pull registry # 3.创建并后台运行 ...

  9. Linux基础命令-查看基本硬件信息

    Linux基础命令-查看基本硬件信息 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.查看CPU信息 [root@node101.yinzhengjie.org.cn ~]# l ...

  10. 监控服务zabbix部署

    目录 1. zabbix介绍 2. zabbix特点 3. zabbix配置文件 4. 部署zabbix 4.1 zabbix服务端安装 4.2 zabbix服务端配置 4.3 zabbix服务端we ...