解析Java中的值傳遞

C++中在進行參數傳遞時,分爲按值傳遞、引用傳遞(&)、按指針傳遞(*),需要自己指定參數傳遞類型。但JAVA表面上只有值傳遞,但真的只是值傳遞嗎?其實並非如此,Java中也分爲按值傳遞、按引用傳遞,只是傳遞類型按照傳入參數的類型而定。

傳遞類型定義

1、值傳遞:在調用函數時將實際參數複製一份傳遞到函數中,這樣在函數中如果對參數進行修改,修改的將是參數副本,將不會影響到實際參數。
2、引用傳遞:在調用函數時將參數的引用進行拷貝(即將實際參數的地址傳遞到函數中),那麼在函數中對參數所進行的修改,其實修改的是參數本身,將影響到實際參數。
在這裏插入圖片描述

Java中的參數傳遞

1、當傳入函數的參數是基本數據類型(byte,boolean,char,short,int ,float,double,long)時,此時參數傳遞進行的是按值傳遞。
2、當傳入函數的參數是引用類型時(基本類型的包裝類、自定義類、數組),此時參數傳遞進行的是引用傳遞。

引用數據類型

引用數據類型包括類(基本類型的包裝類)、接口、數組。那它與基本類型有什麼區別呢?
1、引用數據類型數據其實包含兩部分,一是對象引用(reference),這部分存在棧得局部變量表中,它是指向對象的引用指針;另外一部分是對象本身,它存放在堆中(實際數據)。例如下文中的例子,deliveryType dt=new deliveryType();,dt是一個對象引用類型數據,存放在棧中;而這個對象本身存在堆中,dt指向堆中的對象。非常量基本數據類型變量直接存放在棧中。
2、引用數據類型數據在進行參數傳遞時,使用的是引用傳遞,傳遞進函數的其實就是對象的指針(地址),也就是對象引用類型變量,即上面的dt,而不是對象本身。在函數內拿到對象的地址,可以直接對堆中的對象進行值的修改,這也是它爲什麼可以修改實參的值的原因。

Java的值傳遞分析

public static void change_int(int a){
        a=222;
    }
 public static void change_double(double e){
        e=23.5;
    }    
public static void main(String[] args){
       int  a=243; 
       double e=23.4;
       change_int(a);
       change_double(e);
       System.out.println("a的值"+a);  //243  沒有被修改
       System.out.println("e的值"+e);  //23.4 沒有被修改
       }

通過代碼實踐發現,對於基本類型數據,將其傳入函數後,在方法內改變參數的值,只是修改了形參(參數副本)的值,不會影響實參(原參數)的值。
它是將實參的值在該函數所屬的棧幀的局部變量表中拷貝了一份,改變的只是局部變量表中形參的值,當函數執行完畢,該棧幀銷燬,形參也銷燬,不會影響實參值。

Java的引用傳遞分析

public class deliveryType {
    private int age;
     public static  int high=170;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void change_age(deliveryType dt){
        dt.setAge(19);
    }
    public void change_age2(deliveryType dt){
        dt=new deliveryType();
        dt.setAge(30);
    }
    public static void change_high(int high){
        high=232323;
    }
    public static void change_array(int[] array){
        array[3]=100;
    }
        public static void main(String[] args){
       int   []array={1,2,3,4,5};
       deliveryType dt=new deliveryType();
        dt.setAge(12);
        change_high(high);
        change_array(array);
        dt.change_age(dt);
        System.out.println("第一次修改之後的年齡"+dt.getAge()); //19
        dt.change_age2(dt);
        System.out.println("第二次修改之後的年齡"+dt.getAge()); //19
        System.out.println("身高的值"+high);  //170
        System.out.println("數組改完之後的值爲"+array[3]); //100
    }
}

從以上代碼以及結果發現,除第二次修改age沒有修改實參成功(注意:有兩種修改age的函數),以及修改high沒有成功,其它修改實參成功。
1、修改high失敗的原因:因爲它是以high爲變量傳入(基本數據類型),而不是以對象dt傳入(引用數據類型)。
2、第一次修改age成功的原因:是爲傳入的是對象的對象引用類型變量dt,傳入的是引用數據類型,這裏進行的是引用傳遞(將對象dt的引用變量傳入,該引用變量的值爲對象dt的地址值),所以可以在函數裏直接對對象進行操作,修改堆中的對象dt的age值。
3、第二次修改age失敗的原因是因爲傳入的引用變量dt本來指向對象dt,但它在函數中指向了新的對象,修改的也是新的對象中的age值,那原本對象中的age值沒有被改變,自然修改失敗。
4、數組值修改成功的原因:數組同對象一樣,存儲在堆中,傳入函數的變量其實是一個對象引用類型變量,這個對象引用類型變量的值是數組的地址值。在函數中對數組進行改變,其實就相當於利用對象引用類型變量的值,到堆中對數組本身進行值得修改,所以實參修改成功。
綜上所述,Java中引用數據類型之所以進行的是引用傳遞,是因爲它傳入的不是值本身,而是引用,也就是對象引用類型變量,它保存着指向堆中的對象的指針(地址)。所以在函數內對其進行改變,其實就是按着指針去到堆中對堆中的數據進行更改。所以實參的值也就改變了。

String、Integer、Double等包裝類的參數傳遞類型分析

按理來說,這些包裝類都是屬於類,這種類型的變量屬於引用類型數據,進行的是引用傳遞,但你會發現它卻表現爲值傳遞,這是爲什麼呢?

 public static void main(String[] args){ 
       Double  e=new Double(23.4);
       Integer d=new Integer(2342);
       String b="bbb";
       String c=new String("bbb");
        change_string(b);
        change_string(c);
        change_double(e);
        change_Integer(d);
        System.out.println("b的值"+b);//bbb
        System.out.println("c的值"+c);//bbb
        System.out.println("d的值"+d);//2342
        System.out.println("e的值"+e);//23.4
    }

    public static void change_Integer(Integer c){
        c=222222;
      //  c=new Integer(231321);
    }

    public static void change_string(String b){
         //b=new String ("更改");
          b="更改";
         //b=null;
    }
    public static void change_double(Double e){
        e=23.5;
    }

運行以上代碼,你會發現,所有的對象的值,在函數內進行修改,但在main()中的變量值還是原值。這是因爲它們都是常量類,類中的值是final變量,一經初始化就不可改變,在函數中進行修改時,若新值已存在,則將副本引用變量指向該新值存在的位置,否則在常量池中或者堆中,新建了一個實例,並將副本引用變量指向它。原來指向的對象中的數據沒有改變。

String以及Integer等包裝類都是final類(final類中的成員變量可以被定義爲final或非final形式),但String類以及基本類型的包裝類的所有得變量都被final修飾,都是常量,關鍵是value這個變量被修飾爲常量,因此無法被改變,只能讓對象引用變量進行重新指向。不改變原來對象得值,這也是String、Integer等引用類型變量表現爲值傳遞的原因。
(下面節選String和Integer的源碼)

public final class String implements Serializable, Comparable<String>, CharSequence {
    @Stable
    private final byte[] value;
    private final byte coder;
    private int hash;
    private static final long serialVersionUID = -6849794470754667710L;
    static final boolean COMPACT_STRINGS = true;
    private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
    public static final Comparator<String> CASE_INSENSITIVE_ORDER = new String.CaseInsensitiveComparator();
    static final byte LATIN1 = 0;
    static final byte UTF16 = 1;
public final class Integer extends Number implements Comparable<Integer> {
    public static final int MIN_VALUE = -2147483648;
    public static final int MAX_VALUE = 2147483647;
    public static final Class<Integer> TYPE = Class.getPrimitiveClass("int");
    static final char[] digits = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
    static final byte[] DigitTens = new byte[]{48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57};
    static final byte[] DigitOnes = new byte[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57};
    static final int[] sizeTable = new int[]{9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, 2147483647};
    private final int value;
    public static final int SIZE = 32;
    public static final int BYTES = 4;
    private static final long serialVersionUID = 1360826667806852920L;

總結

1、當傳入函數的參數是基本數據類型(byte,boolean,char,short,int ,float,double,long)時,此時參數傳遞進行的是按值傳遞,傳入函數內的是值的拷貝。
2、當傳入函數的參數是引用類型時(基本類型的包裝類、自定義類、數組),此時參數傳遞進行的是引用傳遞,傳入的是對象的對象引用的拷貝,該對象引用的值爲對象的地址值。
3、基本類型的包裝類以及String這些常量類,因爲類中的值是被final修飾的,所以在函數內部無法進行值得修改,只能將對象引用重新指向新的對象(非原來對象),因此表現爲值傳遞。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章