辨析Java方法參數中的值傳遞和引用傳遞

小方法大門道

小瓜瓜作爲一個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

clipboard.png

  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

當傳入參數爲包裝類型時,爲對象的引用地址拷貝。那麼既然是引用拷貝爲什麼還是沒有更改原來的包裝類型的變量值呢?

clipboard.png

這是因爲Java中的自動裝箱機制,當在方法中執行 flg = true 時,實際在編譯後執行的是 flg = Boolean.valueOf(true),即又會產生一個新的Boolean對象。同理Integer num也是如此。

clipboard.png

方法參數爲類的對象引用時

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}

clipboard.png

結論:對於引用類型的方法參數,會將外部變量的引用地址,複製一份到方法的局部變量中,兩個地址指向同一個對象。所以如果通過操作副本引用的值,修改了引用地址的對象,此時方法以外的引用此地址對象也會被修改。(兩個引用,同一個地址,任何修改行爲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}

clipboard.png

有了上面的知識之後,我們會發現這個方法中的引用地址交換,只不過是一個把戲而已,只是對方法中的兩個局部變量的對象引用值進行了交換,不會對原變量引用產生任何影響的。

一個方法返回兩個返回值

Java方法中只能Return一個返回值,那麼如何在一個方法中返回兩個或者多個返回值呢?(⊙v⊙)嗯,我們可以通過使用泛型來定義一個二元組來達到我們的目的。

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);
    }
}

結果爲:

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<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);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章