Java之路:引用傳遞
1、引用傳遞
引用傳遞也稱為傳地址,指的是在方法呼叫時,傳遞的引數是按引用進行傳遞,其實傳遞的是引用的地址,也就是變數所對應的記憶體空間的地址。
方法呼叫時,實際引數的引用——地址被傳遞給方法中相對應的形式引數,即形式引數和實際引數擁有相同的儲存單元。在方法執行過程中,對形式引數的操作實際上就是對實際引數的操作,因此形式引數值的改變將會影響實際引數的值。
2、堆記憶體與棧記憶體
(1)堆記憶體:堆記憶體可以理解為一個物件的具體資訊,每一個物件儲存的只是屬性資訊,每一塊堆記憶體的開闢都要通過關鍵字new來完成。
(2)棧記憶體:可以理解為一個整型變數(只能夠儲存一個數值),其中儲存的是一塊(只能儲存一塊)堆記憶體空間的記憶體地址數值,但是為了方便理解,現在可以假設其儲存的是物件的名字。
class Book {
String title;
double price;
public void printInfo() {
System.out.println("title:" + this.title);
System.out.println("price:" + this.price);
}
}
public class Reference {
public static void main(String[] args) {
Book book = null; // 宣告物件
book = new Book(); // 例項化一個物件
book. title = "Java程式設計";
book.price = 39.8;
book.printInfo(); // 通過物件呼叫,不是直接呼叫
}
}
【結果】
下面看一下該程式的內部執行過程:
如果把“book = new Book() ;”註釋掉,此時會出現如下的錯誤提示:
“NullPointerException”表示空指向異常,指的是使用了一個未例項化的物件(未開闢堆記憶體空間的物件)進行了類中屬性或方法的呼叫的時候就會出現本異常資訊。 所以物件使用之前一定要開闢堆記憶體空間。
在程式中一個關鍵字new產生一個物件,就開闢一個新空間,如果有兩個關鍵字new,就表示要開闢兩個新記憶體空間,此處的兩個物件都佔有各自的記憶體空間,彼此的操作應該是獨立的。也即,只要用了new,不管在何種情況下,都表示要開闢新的記憶體空間。
3、引用資料型別的傳遞
在Java中,類本身就是引用資料型別,而對於引用資料型別實際上就相當於其他語言之中的指標概念。
在Java中對於方法引數的傳遞,物件是傳遞引用,基本資料型別是傳遞值。
下面看一個傳遞基本資料型別的例子:
public class ReferenceDemo2 {
public static void main(String[] args) {
int i =3,
j = 4;
swap(i, j);
System.out.println("i = " + i);
System.out.println("j = " + j);
}
public static void swap(int i, int j) {
int temp = i;
i = j;
j = temp;
}
}
【結果】
從本範例中可以看到,引用資料型別的傳遞並沒有改變資料本身的值。因為引數中傳遞的是基本型別 i 和 j 的備份,在函式中交換的也是那份備份的值而不是資料本身。
再看下面這個例子:引用傳遞
public class ReferenceDemo2 {
public static void main(String[] args) {
int[] count = {1,2,3,4,5};
System.out.println("方法執行前 : count[0] = " + count[0]);
swap(count);
System.out.println("方法執行後 : count[0] = " + count[0]);
}
public static void swap(int[] count) {
count[0] = 0;
System.out.println("在方內部 : count[0] = " + count[0]);
}
}
【結果】
從本範例中可以看到,在方法中傳遞引用資料型別int陣列,實際上傳遞的是其引用count的備份,它們都指向陣列物件,在方法中可以改變陣列物件的內容。即:對複製的引用所呼叫的方法更改的是同一個物件。
再來看一個關於物件引用傳遞的例子:
class Person {
String name;
int age;
}
public class ReferenceDemo3 {
public static void main(String[] args) {
Person p1 = null; // 宣告p1,此物件值為null,尚未例項化
Person p2 = null;
p1 = new Person(); // 例項化物件p1
p1.age = 20;
p1.name = "小光";
p2 = p1; // 將p1的引用賦給p2
System.out.println("姓名 : " + p2.name);
System.out.println("年齡 : " + p2.age);
p1 = null;
}
}
【結果】
下而是具體的引用過程:
所謂的引用傳遞,指的是一塊堆記憶體空間,同時被多個棧記憶體所指向。引用傳遞的核心認識:不同的棧記憶體如果指向了同一塊堆記憶體之中,所做的修改將影響所有的棧記憶體。
可以發現,每一塊棧記憶體只能夠儲存一塊堆記憶體的地址,但是反過來,一塊堆記憶體可以同時被多個棧記憶體所指向,在這種情況下,如果要改變某一個棧記憶體的儲存地址內容,則必須先斷開已有的堆記憶體地址連線,才可以指向新的堆記憶體空間,而如果一塊堆記憶體空間沒有任何的棧記憶體所指向的話,那麼這塊空間就將成為垃圾,所有的垃圾將等待被JVM中的GC(Garbage Collector)進行不定期的收集,同時進行記憶體空間的釋放。