资讯专栏INFORMATION COLUMN

辨析Java方法参数中的值传递和引用传递

Aomine / 1399人阅读

摘要:引用数据类型指针存放在局部变量表中,调用方法的时候,副本引用压栈,赋值仅改变副本的引用。方法执行完毕,不再局部变量不再被使用到,等待被回收。

小方法大门道

小瓜瓜作为一个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

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

执行方法逻辑,方法中的局部变量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一个返回值,那么如何在一个方法中返回两个或者多个返回值呢?(⊙v⊙)嗯,我们可以通过使用泛型来定义一个二元组来达到我们的目的。

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

结果为:

first : true, second : 10
完整代码
package com.lingyejun.authenticator;

/**
 * 基本类型,赋值运算=,会直接改变变量的值,原来的值被覆盖掉。
 * 引用类型,赋值运算=,会改变引用中所保存的地址,旧地址被覆盖掉,但原来的对象不会改变。
 * 
 * @Author: lingyejun
 * @Date: 2019/6/16
 * @Describe:
 * @Modified By:
 */
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 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 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 result = returnTwoResult(false,5);

        System.out.println("first : " + result.first + ", second : " + result.second);
    }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/77780.html

相关文章

  • 深度辨析 Python 的 eval() 与 exec()

    摘要:内置函数们能够被提拔出来,这就意味着它们皆有独到之处,有用武之地。因此,掌握内置函数的用法,就成了我们应该点亮的技能。报错包含了内置命名空间中的名称,在控制台中输入,就能发现很多内置函数异常和其它属性的名称。 Python 提供了很多内置的工具函数(Built-in Functions),在最新的 Python 3 官方文档中,它列出了 69 个。 大部分函数是我们经常使用的,例如 p...

    AndroidTraveler 评论0 收藏0
  • Java方法参数传递——值传递or引用传递

    摘要:有种流行的观点说的另外一个特殊之处在于,在方法调用传参数时,是按值传递的,其他普通对象是引用传递。然而这种说法是大大错误的,至少是完全误解了值传递和引用传递的概念。方法调用传参只有一种传递就是值传递。 上篇文章说到Java的String是比较特殊的对象,它是不可变的。 有种流行的观点说String的另外一个特殊之处在于,在方法调用传参数时,String是按值传递的,其他普通对象是引用传...

    Berwin 评论0 收藏0
  • java中传值方式的个人理解

    摘要:接下了,我们调用方法,来尝试改变的值以此验证中的传值方式。我们将作为实参传给方法,形参来接受这个实参,在这里就体现出了两种传参方式的不同。中只有值传递这一种方式,只不过对于引用类型来说,传递的参数是对象的引用罢了。 前言 这几天在整理java基础知识方面的内容,对于值传递还不是特别理解,于是查阅了一些资料和网上相关博客,自己进行了归纳总结,最后将其整理成了一篇博客。 值传递 值传递是指...

    vvpvvp 评论0 收藏0
  • Java基础知识储备一:Java的值传递引用传递

    摘要:每个栈帧中包括局部变量表用来存储方法中的局部变量非静态变量函数形参。操作数栈虚拟机的解释执行引擎被称为基于栈的执行引擎,其中所指的栈就是指操作数栈。指向运行时常量池的引用存储程序执行时可能用到常量的引用。 本篇文章转自微信公众号:Java后端技术 学过Java基础的人都知道:值传递和引用传递是初次接触Java时的一个难点,有时候记得了语法却记不得怎么实际运用,有时候会的了运用却解释不出...

    frontoldman 评论0 收藏0
  • 这一次,彻底解决Java的值传递引用传递

    摘要:操作数栈虚拟机的解释执行引擎被称为基于栈的执行引擎,其中所指的栈就是指操作数栈。基本数据类型的静态变量前面提到方法区用来存储一些共享数据,因此基本数据类型的静态变量名以及值存储于方法区的运行时常 本文旨在用最通俗的语言讲述最枯燥的基本知识 学过Java基础的人都知道:值传递和引用传递是初次接触Java时的一个难点,有时候记得了语法却记不得怎么实际运用,有时候会的了运用却解释不出原理,而...

    Lavender 评论0 收藏0

发表评论

0条评论

Aomine

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<