淺談Java值傳遞和引用傳遞

值與引用

值類型

值類型默認存放在棧中,如一些原始數據類型的局部變量和對象的引用(String, 數組)不存放對象內容

引用類型

引用類型存放在堆中,準確說堆中存放的是 new 創建的對象,而指向對象的引用是存放在棧中。

特殊情況

字符串是一個特殊包裝類,其引用是存放在棧裏的,而對象內容必須根據創建方式不同決定(常量池和堆)。有的是編譯期就已經創建好,存放在字符串常量池中,而有的是運行時才被創建,使用new關鍵字,存放在堆中。

棧和堆

  • 棧使用一級緩存,由系統自動分配,容量較小,調用完立刻釋放,所以用於存儲容量小的內容(比如基本數據類型的局部變量和引用)
  • 堆使用二級緩存,由垃圾回收器回收,並不是一成爲孤兒對象就會被回收,所以可以存放容量大點的內容。

值傳遞和引用傳遞

  • 按值調用(call by value)表示方法接收的是調用者提供的值
// 方法不能修改基本類型的參數
private static void swap(int a, int b){
    int temp = a;
    a = b;
    b = temp;
}

public static void main(String[] args) {
    int a = 0;
    int b = 1;
    swap(a,b);
    System.out.println("a:"+a+"; b:"+b);
    int temp = a;
    a = b;
    b = temp;
    System.out.println("a:"+a+"; b:"+b);
}

結果是:
a:0; b:1
a:1; b:0

基本數據類型無法修改,那麼 String 不是基本數據類型能不能通過方法實現交換呢?

private static void swap(String a, String b){
    String temp = a;
    a = b;
    b = temp;
}

public static void main(String[] args) {
    String a = "0";
    String b = "1";
    swap(a,b);
    System.out.println("a:"+a+"; b:"+b);
    String temp = a;
    a = b;
    b = temp;
    System.out.println("a:"+a+"; b:"+b);
}

結果仍然是:
a:0; b:1
a:1; b:0

這是爲什麼呢?
原因很簡單,我們打開源碼看看 String 類型

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
    
    }

String 被 final 修飾,同時 value[] 也被 final 修飾,所以不可以修改 String 對象。

那麼新的問題來了?爲什麼我們日常操作的時候經常改變 String 的值有是怎麼實現的呢?很簡單看源碼

public String substring(int beginIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    int subLen = value.length - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    // 正常情況下,可以看出 subString 的結果是重新 new 了一個 String 對象返回,與原有的 String 對象不同
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

相信通過上面這些例子,你已經簡單認識到了什麼是 call by value 了,也對 String 這個有了初步認識,接下來我們介紹引用調用

  • 按引用調用(call by reference)表示方法接收的是調用者提供的變量地址
private static void swap(int[] a, int x, int y){
    int temp = a[x];
    a[x] = a[y];
    a[y] = temp;
}

public static void main(String[] args) {
    int[] a = {0,1};
    // 調換第 0 和第 1 的位置
    swap(a,0,1);
    System.out.println("a[0]:"+a[0]+"; a[1]:"+a[1]);
}

結果顯然是:a[0]:1; a[1]:0

這裏擴展一一下關於數組的小知識,數組爲什麼從 0 開始計數
數組用連續的內存空間存儲一組具有相同類型線性數據結構。
同時數組有個最大的殺手鐗也就是 按照下標隨機訪問。其實原理很簡單,我們把首個數組對象的地址記錄下來,比如 int[] a = {0,1} 那麼首地址也就是 a[0], 所以 a[1] = a[0]+ 1*size; 更便於公式計算。當然這只是一種說法,更多的是因爲約定俗成,大家都從 0 開始了,行有行規自然都從 0 開始了。

Java 對對象採用的不是引用傳遞,實際上是按值傳遞

一個方法不能修改一個基本類型的參數,而對象引用作爲參數就不同。方法得到的對象引用的拷貝,對象引用及其他拷貝同時引用同一個對象。
所以嚴格意義上講 Java 都是按值傳遞,不同的是對象傳入的是引用,引用在調用方法前後都沒有發生變化。


如果覺得我的文章不錯的話,可以關注一下我的微信公衆號 搜索“ Fenmu”,或者掃一掃下面的二維碼

Fenmu

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