小方法大门道

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

public class MethodParamsPassValue {

    public static void doErrorHandle() {
boolean a = false;
int b = 5;
passBaseValue(a, b);
if (a == true || b == 10) {
System.out.println("Execute Something");
} else {
System.out.println("param result wrong");
}
} public static void passBaseValue(boolean flg, int num) {
flg = true;
num = 10;
} public static void main(String[] args) {
doErrorHandle();
}
}

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

究其原因

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

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

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

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

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

public class MethodParamsPassValue {

    public static void passBaseValue(boolean flg, int num) {
flg = true;
num = 10;
} public static void main(String[] args) {
boolean a = false;
int b = 5;
System.out.println("a : " + a + " b : " + b);
passBaseValue(a, b);
System.out.println("a : " + a + " b : " + b);
}
}

返回结果

a : false b : 5
a : false b : 5

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

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

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

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

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

public class MethodParamsPassValue {

    public static void passReferenceValue(Boolean flg, Integer num) {
flg = true;
num = 10;
} public static void main(String[] args) {
Boolean a = false;
Integer b = 5;
System.out.println("a : " + a + " b : " + b);
passReferenceValue(a, b);
System.out.println("a : " + a + " b : " + b);
}
}

结果为  

a : false b : 5
a : false b : 5

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

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

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

public class ParamObject {

    private boolean flg;

    private int num;

    public ParamObject(boolean flg, int num) {
this.flg = flg;
this.num = num;
} public boolean isFlg() {
return flg;
} public void setFlg(boolean flg) {
this.flg = flg;
} public int getNum() {
return num;
} public void setNum(int num) {
this.num = num;
} @Override
public String toString() {
return "ParamObject{" +
"flg=" + flg +
", num=" + num +
'}';
}
}
public class MethodParamsPassValue {

    public static void passObjectValue(ParamObject paramObject) {
paramObject.setFlg(true);
paramObject.setNum(10);
} public static void main(String[] args) {
ParamObject a = new ParamObject(false, 5);
System.out.println(a);
passObjectValue(a);
System.out.println(a);
}
}  

结果为

ParamObject{flg=false, num=5}
ParamObject{flg=true, num=10}

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

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

public class MethodParamsPassValue {

    public static void swapObjectReference(ParamObject object1, ParamObject object2) {
ParamObject temp = object1;
object1 = object2;
object2 = temp;
} public static void main(String[] args) {
ParamObject a = new ParamObject(true, 1);
ParamObject b = new ParamObject(false, 2);
System.out.println("a : " + a + " b : " + b);
swapObjectReference(a, b);
System.out.println("a : " + a + " b : " + b);
}
}  

结果为

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

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

一个方法返回两个返回值

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

public class TwoTuple<A, B> {

    public final A first;

    public final B second;

    public TwoTuple(A a, B b) {
first = a;
second = b;
} public String toString() {
return "(" + first + ", " + second + ")";
}
}

  

public class MethodParamsPassValue {

    public static TwoTuple<Boolean, Integer> returnTwoResult(Boolean flg, Integer num) {
flg = true;
num = 10;
return new TwoTuple<>(flg, num);
} public static void main(String[] args) {
TwoTuple<Boolean,Integer> result = returnTwoResult(false,5);
System.out.println("first : " + result.first + ", second : " + result.second);
}
}

  

完整代码

package com.lingyejun.authenticator;

public class MethodParamsPassValue {

    public static void doErrorHandle() {
boolean a = false;
int b = 5;
passBaseValue(a, b);
if (a == true || b == 10) {
System.out.println("Execute Something");
} else {
System.out.println("param result wrong");
}
} /**
* 基本类型,赋值运算=,会直接改变变量的值,原来的值被覆盖掉
* 引用类型,复制运算=,会改变引用中所保存的地址,旧地址被覆盖掉,但原来的对象不会改变。
*
* @param flg
* @param num
*/
public static void passBaseValue(boolean flg, int num) {
flg = true;
num = 10;
} public static void passReferenceValue(Boolean flg, Integer num) {
flg = true;
num = 10;
} public static void passObjectValue(ParamObject paramObject) {
paramObject.setFlg(true);
paramObject.setNum(10);
} public static void swapObjectReference(ParamObject object1, ParamObject object2) {
ParamObject temp = object1;
object1 = object2;
object2 = temp;
} public static TwoTuple<Boolean, Integer> returnTwoResult(Boolean flg, Integer num) {
flg = true;
num = 10;
return new TwoTuple<>(flg, num);
} public static void main(String[] args) { doErrorHandle(); System.out.println("============================"); boolean initFlg = false;
int initNum = 5; System.out.println("init flg : " + initFlg + " init num : " + initNum); passBaseValue(initFlg, initNum); System.out.println("init flg : " + initFlg + " init num : " + initNum); System.out.println("============================"); Boolean referenceFlg = false;
Integer referenceNum = 5; System.out.println("reference flg : " + referenceFlg + " reference num : " + referenceNum); passReferenceValue(referenceFlg, referenceNum); System.out.println("reference flg : " + referenceFlg + " reference num : " + referenceNum); System.out.println("============================"); ParamObject paramObject = new ParamObject(false, 5); System.out.println(paramObject); passObjectValue(paramObject); System.out.println(paramObject); System.out.println("============================"); ParamObject object1 = new ParamObject(true, 1);
ParamObject object2 = new ParamObject(false, 2); System.out.println("object1 : " + object1 + " object2 : " + object2); swapObjectReference(object1, object2); System.out.println("object1 : " + object1 + " object2 : " + object2); System.out.println("============================"); TwoTuple<Boolean,Integer> result = returnTwoResult(false,5); System.out.println("first : " + result.first + ", second : " + result.second);
}
}

  

参考文章:

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. vue-router中的router-view的困扰

    刚开始接触vue的时候有很长一段时间被router-view的渲染困扰着,不知道为什么明明有很多router-link和不同的router-view,但是为什么渲染的时候不会出现错乱呢 这是我自己写的 ...

  2. 什么是SAP Intelligent Robitic Process Automation - iRPA

    所谓智慧企业,一个特征就是具备将复杂但低附加值的重复流程通过自动化的方式完成的能力.通过自动化,从而将宝贵的人力资源投入到更高附加值的工作中去,比如提供产品和服务的品质,提升用户体验.SAPGUI时代 ...

  3. [LeetCode] 543. 二叉树的直径 ☆(递归、数最大深度)

    描述 给定一棵二叉树,你需要计算它的直径长度.一棵二叉树的直径长度是任意两个结点路径长度中的最大值.这条路径可能穿过根结点. 示例 :给定二叉树 1 / \ 2 3 / \ 4 5 返回 3, 它的长 ...

  4. thrift简单示例 (基于C++)

    这个thrift的简单示例, 来源于官网 (http://thrift.apache.org/tutorial/cpp), 因为我觉得官网的例子已经很简单了, 所以没有写新的示例, 关于安装的教程, ...

  5. tar.bz2解压异常

    问题描述: [root@mvp-dd ~]# tar jxf ffmpeg-.tar.bz2 tar (child): bzip2: Cannot exec: No such file or dire ...

  6. 【Spring Boot】Spring Boot之自定义配置参数绑定到Java Bean

    一.@Value方式 1.我的配置文件:application-dev.yml # 自定义项目配置 startproject: pro1: pro2: pro3: pro4: lists: - ' - ...

  7. MongoDB 分片问题汇总

    分片是MongoDB的扩展方式,通过分片能够增加更多的机器来用对不断增加的负载和数据,还不影响应用. 1.分片简介 分片是指将数据拆分,将其分散存在不同机器上的过程.有时也叫分区.将数据分散在不同的机 ...

  8. (MYSQL)回表查询原理,利用联合索引实现索引覆盖

    一.什么是回表查询? 这先要从InnoDB的索引实现说起,InnoDB有两大类索引: 聚集索引(clustered index) 普通索引(secondary index) InnoDB聚集索引和普通 ...

  9. 关于c++中的类型转换符

    const_cast(链接) 用来去掉const或volatile属性 volatile: 用于并行设备的硬件寄存器(状态寄存器), 中断服务子程序中会访问到的非自动变量, 多线程中被几个任务共享的变 ...

  10. Alpha冲刺(9/10)——追光的人

    1.队友信息 队员学号 队员博客 221600219 小墨 https://www.cnblogs.com/hengyumo/ 221600240 真·大能猫 https://www.cnblogs. ...