1. 程式人生 > >java基礎之----基本資料型別和引用資料型別的引數傳遞過程(一)

java基礎之----基本資料型別和引用資料型別的引數傳遞過程(一)

值傳遞:方法呼叫時,實際引數把它的值傳遞給對應的形式引數,方法執行中形式引數值的改變不影響實際引數的值。

引用傳遞:也稱為傳地址。方法呼叫時,實際引數的引用(地址,而不是引數的值)被傳遞給方法中相對應的形式引數,在方法執行中,對形式引數的操作實際上就是對實際引數的操作,方法執行中形式引數值的改變將會影響實際引數的值。

Java引數按值傳遞 面試題:當一個物件被當作引數傳遞到一個方法後,此方法可改變這個物件的屬性,並可返回變化後的結果,那麼這裡到底是值傳遞還是引用傳遞? 答:是值傳遞。Java 程式語言只有值傳遞引數。當一個物件例項作為一個引數被傳遞到方法中時,引數的值就是該物件的引用的一個副本。指向同一個物件,物件的內容可以在被呼叫的方法中改變,但物件的引用(不是引用的副本)是永遠不會改變的。

在 Java 應用程式中永遠不會傳遞物件,而只傳遞物件引用。因此是按引用傳遞物件。但重要的是要區分引數是如何傳遞的,這才是該節選的意圖。Java 應用程式按引用傳遞物件這一事實並不意味著 Java 應用程式按引用傳遞引數。引數可以是物件引用,而 Java 應用程式是按值傳遞物件引用的。

Java 應用程式中的變數可以為以下兩種型別之一:引用型別或基本型別。當作為引數傳遞給一個方法時,處理這兩種型別的方式是相同的。兩種型別都是按值傳遞的;沒有一種按引用傳遞。

按 值傳遞意味著當將一個引數傳遞給一個函式時,函式接收的是原始值的一個副本。因此,如果函式修改了該引數,僅改變副本,而原始值保持不變。按引用傳遞意味 著當將一個引數傳遞給一個函式時,函式接收的是原始值的記憶體地址,而不是值的副本。因此,如果函式修改了該引數的值,呼叫程式碼中的原始值也隨之改變。如果 函式修改了該引數的地址,呼叫程式碼中的原始值不會改變.

當傳遞給函式的引數不是引用時,傳遞的都是該值的一個副本(按值傳遞)。區別在於引用。在 C++ 中當傳遞給函式的引數是引用時,您傳遞的就是這個引用,或者記憶體地址(按引用傳遞)。在 Java 應用程式中,當物件引用是傳遞給方法的一個引數時,您傳遞的是該引用的一個副本(按值傳遞),而不是引用本身。

Java 應用程式按值傳遞引數(引用型別或基本型別),其實都是傳遞他們的一份拷貝.而不是資料本身.(不是像 C++ 中那樣對原始值進行操作。)

例1:

//在函式中傳遞基本資料型別,

public class Test {

public static void change(int i, int j) {   
    int temp = i;   
    i = j;   
    j = temp;   
}   

public static void main(String[] args) {   
    int a = 3;   
    int b = 4;   
    change(a, b);   

    System.out.println("a=" + a);   
    System.out.println("b=" + b);   
}   

} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 結果為: a=3 b=4 原因就是 引數中傳遞的是 基本型別 a 和 b 的拷貝,在函式中交換的也是那份拷貝的值 而不是資料本身;

例2: //傳的是引用資料型別

public class Test {

public static void change(int[] counts) {   
    counts[0] = 6;   
    System.out.println(counts[0]);   
}   

public static void main(String[] args) {   
    int[] count = { 1, 2, 3, 4, 5 };   
    change(count);   
}   

} 1 2 3 4 5 6 7 8 9 10 11 12 在方法中 傳遞引用資料型別int陣列,實際上傳遞的是其引用count的拷貝,他們都指向陣列物件,在方法中可以改變陣列物件的內容。即:對複製的引用所呼叫的方法更改的是同一個物件。

例3: //物件的引用(不是引用的副本)是永遠不會改變的

class A { int i = 0; }

public class Test {

public static void add(A a) {   
    a = new A();   
    a.i++;   
}   

public static void main(String args[]) {   
    A a = new A();   
    add(a);   
    System.out.println(a.i);   
}   

} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 輸出結果是0 在該程式中,物件的引用指向的是A ,而在change方法中,傳遞的引用的一份副本則指向了一個新的OBJECT,並對其進行操作。 而原來的A物件並沒有發生任何變化。 引用指向的是還是原來的A物件。

例4: String 不改變,陣列改變

public class Example { String str = new String(“good”);

char[] ch = { 'a', 'b', 'c' };   

public static void main(String args[]) {   
    Example ex = new Example();   
    ex.change(ex.str, ex.ch);   
    System.out.print(ex.str + " and ");   
    System.out.println(ex.ch);   
}   

public void change(String str, char ch[]) {   
    str = "test ok";   
    ch[0] = 'g';   
}   

} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 程式3輸出的是 good and gbc. String 比較特別,看過String 程式碼的都知道, String 是 final的。所以值是不變的。 函式中String物件引用的副本指向了另外一個新String物件,而陣列物件引用的副本沒有改變,而是改變物件中資料的內容. 對於物件型別,也就是Object的子類,如果你在方法中修改了它的成員的值,那個修改是生效的,方法呼叫結束後,它的成員是新的值,但是如果你把它指向一個其它的物件,方法呼叫結束後,原來對它的引用並沒用指向新的物件。

Java引數,不管是原始型別還是引用型別,傳遞的都是副本(有另外一種說法是傳值,但是說傳副本更好理解吧,傳值通常是相對傳址而言)。 如果引數型別是原始型別,那麼傳過來的就是這個引數的一個副本,也就是這個原始引數的值,這個跟之前所談的傳值是一樣的。如果在函式中改變了副本的 值不會改變原始的值. 如果引數型別是引用型別,那麼傳過來的就是這個引用引數的副本,這個副本存放的是引數的地址。如果在函式中沒有改變這個副本的地址,而是改變了地址中的 值,那麼在函式內的改變會影響到傳入的引數。如果在函式中改變了副本的地址,如new一個,那麼副本就指向了一個新的地址,此時傳入的引數還是指向原來的 地址,所以不會改變引數的值。 ( 物件包括物件引用即地址和物件的內容)

a.傳遞值的資料型別:八種基本資料型別和String(這樣理解可以,但是事實上String也是傳遞的地址,只是string物件和其他對 象是不同的,string物件是不能被改變的,內容改變就會產生新物件。那麼StringBuffer就可以了,但只是改變其內容。不能改變外部變數所指 向的記憶體地址)。 b.傳遞地址值的資料型別:除String以外的所有複合資料型別,包括陣列、類和介面

下面舉例說明: 在 Java 應用程式中永遠不會傳遞物件,而只傳遞物件引用。因此是按引用傳遞物件。但重要的是要區分引數是如何傳遞的,這才是該節選的意圖。Java 應用程式按引用傳遞物件這一事實並不意味著 Java 應用程式按引用傳遞引數。引數可以是物件引用,而 Java 應用程式是按值傳遞物件引用的。 Java 應用程式中的變數可以為以下兩種型別之一:引用型別或基本型別。當作為引數傳遞給一個方法時,處理這兩種型別的方式是相同的。兩種型別都是按值傳遞的;沒有一種按引用傳遞。 按 值傳遞意味著當將一個引數傳遞給一個函式時,函式接收的是原始值的一個副本。因此,如果函式修改了該引數,僅改變副本,而原始值保持不變。按引用傳遞意味 著當將一個引數傳遞給一個函式時,函式接收的是原始值的記憶體地址,而不是值的副本。因此,如果函式修改了該引數,呼叫程式碼中的原始值也隨之改變。 當傳遞給函式的引數不是引用時,傳遞的都是該值的一個副本(按值傳遞)。區別在於引用。在 C++ 中當傳遞給函式的引數是引用時,您傳遞的就是這個引用,或者記憶體地址(按引用傳遞)。在 Java 應用程式中,當物件引用是傳遞給方法的一個引數時,您傳遞的是該引用的一個副本(按值傳遞),而不是引用本身。 Java 應用程式按值傳遞所有引數,這樣就製作所有引數的副本,而不管它們的型別。 class Test { public static void main(String args[]) { int val; StringBuffer sb1, sb2;

val = 10; sb1 = new StringBuffer(“apples”); sb2 = new StringBuffer(“pears”); System.out.println("val is " + val); System.out.println(“sb1 is " + sb1); System.out.println(“sb2 is " + sb2); System.out.println(””);

System.out.println(“calling modify”); //按值傳遞所有引數 modify(val, sb1, sb2); System.out.println(“returned from modify”); System.out.println("");

System.out.println("val is " + val); System.out.println("sb1 is " + sb1); System.out.println("sb2 is " + sb2); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 }

public static void modify(int a, StringBuffer r1, StringBuffer r2) { System.out.println(“in modify…”); a = 0; r1 = null; //1 r2.append(” taste good”); System.out.println(“a is ” + a); System.out.println(“r1 is ” + r1); System.out.println(“r2 is ” + r2); } }

Java 應用程式的輸出

val is 10 sb1 is apples sb2 is pears

calling modify in modify… a is 0 r1 is null r2 is pears taste good returned from modify

val is 10 sb1 is apples sb2 is pears taste good

這段程式碼聲明瞭三個變數:一個整型變數和兩個物件引用。設定了每個變數的初始值並將它們打印出來。然後將所有三個變數作為引數傳遞給 modify 方法。

modify 方法更改了所有三個引數的值:

將第一個引數(整數)設定為 0。 將第一個物件引用 r1 設定為 null。 保留第二個引用 r2 的值,但通過呼叫 append 方法更改它所引用的物件(這與前面的 C++ 示例中對指標 p 的處理類似)。

當執行返回到 main 時,再次打印出這三個引數的值。正如預期的那樣,整型的 val 沒有改變。物件引用 sb1 也沒有改變。如果 sb1 是按引用傳遞的,正如許多人聲稱的那樣,它將為 null。但是,因為 Java 程式語言按值傳遞所有引數,所以是將 sb1 的引用的一個副本傳遞給了 modify 方法。當 modify 方法在 //1 位置將 r1 設定為 null 時,它只是對 sb1 的引用的一個副本進行了該操作,而不是像 C++ 中那樣對原始值進行操作。

另外請注意,第二個物件引用 sb2 打印出的是在 modify 方法中設定的新字串。即使 modify 中的變數 r2 只是引用 sb2 的一個副本,但它們指向同一個物件。因此,對複製的引用所呼叫的方法更改的是同一個物件。 傳值—傳遞基本資料型別引數 public class PassValue{ static void exchange(int a, int b){//靜態方法,交換a,b的值 int temp; temp = a; a = b; b = temp; } public static void main(String[] args){ int i = 10; int j = 100; System.out.println(“before call: ” + “i=” + i + “\t” + “j = ” + j);//呼叫前 exchange(i, j); //值傳遞,main方法只能呼叫靜態方法 System.out.println(“after call: ” + “i=” + i + “\t” + “j = ” + j);//呼叫後 } } 執行結果: before call: i = 10 j = 100 after call: i = 10 j = 100 說明:呼叫exchange(i, j)時,實際引數i,j分別把值傳遞給相應的形式引數a,b,在執行方法exchange()時,形式引數a,b的值的改變不影響實際引數i和j的值,i和j的值在呼叫前後並沒改變。 引用傳遞—物件作為引數 如果在方法中把物件(或陣列)作為引數,方法呼叫時,引數傳遞的是物件的引用(地址),即在方法呼叫時,實際引數把對物件的 引用(地址)傳遞給形式引數。這是實際引數與形式引數指向同一個地址,即同一個物件(陣列),方法執行時,對形式引數的改變實際上就是對實際引數的改變, 這個結果在呼叫結束後被保留了下來。 class Book{ String name; private folat price; Book(String n, float ){ //構造方法 name = n; price = p; } static void change(Book a_book, String n, float p){ //靜態方法,物件作為引數 a_book.name = n; a_book.price = p; } public void output(){ //例項方法,輸出物件資訊 System.out.println(“name: ” + name + “\t” + “price: ” + price); } } public class PassAddr{ public static void main(String [] args){ Book b = new Book(“java2”, 32.5f); System.out.print(“before call:\t”); //呼叫前 b.output(); b.change(b, “c++”, 45.5f); //引用傳遞,傳遞物件b的引用,修改物件b的值 System.out.print(“after call:\t”); //呼叫後 b.output(); } } 執行結果: before call: name:java2 price:32.5 after call: name:c++ price:45.5 說明:呼叫change(b,”c++”,45.5f)時,物件b作為實際引數,把引用傳遞給相應的形式引數a_book,實際上a_book也指向同一 個物件,即該物件有兩個引用名:b和a_book。在執行方法change()時,對形式引數a_book操作就是對實際引數b的操作。

基本資料型別引數傳遞

class argDemo { public static void main(String[] args) { int x = 3; change(x); System.out.println("x = "+x); } public static void change(int x) { x = 4; } } 1 2 3 4 5 6 7 8 9 10 //引用型別資料引數傳遞 class Demo { int x = 3; public static void main(String[] args) { Demo d = new Demo(); d.x = 9; change(d); System.out.println(“d.x = ” + d.x); } public static void change(Demo d) { d.x = 4; } } 現在我們來分別對這兩對程式碼的執行程分析一下。 一、對於基本資料型別引數傳遞程式碼的執行過程分析:

1.main方法進棧記憶體,main方法中有基本資料型別變數int x;

2.為main方法中的變數x賦值為3;

3.呼叫change(x)方法,則change方法進棧;

4.為change方法變數x賦值為4;

5.跳出change方法,同時change方法出棧,釋放所有change方法和change方法中的x,即把x=4釋放掉;

6.執行列印語句,些時的棧中只有main方法中的x,那麼打印出的x=3;

7.跳出main方法,結束程式。

我們來看一下列印結果與我們的分析是不是一致的?

二、對於引用資料型別引數傳遞程式碼的執行過程分析:

1.main方法進棧記憶體,main方法中有一個類型別變數Demo d;

2.new建立Demo物件,在堆記憶體中開闢一個空間,並把空間地址傳給d(我們這裡假設為0x0078),併為該地址中的x初始化為0,然後把3賦給x;

3.把d所指堆記憶體(0x0078)中的x賦為9;

4.呼叫change(d)方法,change方法進棧,change方法中的物件d就是main方法中的d,指向之前的堆記憶體地址(0x0078);

5.把d所指堆記憶體(0x0078)中的x賦為4;

6.跳出change方法,同時change方法出棧,釋放change方法和方法中的物件d;

7.執行列印語句,些時的棧中也是隻有main方法,並且d指向堆記憶體(0x0078),該地址中的x就是步驟5中的值4;

8.跳出main方法,結束程式。

我們看看結果:

我們從兩個程式碼打印出的結果可以看出結果與我們的分析是完全一致的。

那麼,基本資料型別引數和引用資料型別引數的過程就是我們上面分析的過程。