1. 程式人生 > >java引數傳遞:值傳遞

java引數傳遞:值傳遞

 簡述棧、堆和方法區的用法

通常我們定義一個基本資料型別的變數非成員變數,物件的成員變數和物件一起放在堆中)、一個物件的引用、還有就是函式呼叫的現場儲存都使用JVM中的棧(stack)空間

通過new關鍵字和構造器建立的物件則放在堆(heap)空間,沒有被引用的物件就成為“垃圾”,因此堆是垃圾收集器(GC)管理的主要區域。由於現在的垃圾收集器都採用分代收集演算法,所以堆空間還可以細分為新生代和老年代,再具體一點可以分為Eden、Survivor、Tenured。堆是執行緒共享的記憶體區域。

方法區(method area)用於儲存已經被JVM載入的類資訊常量靜態變數JIT編譯器編譯後的程式碼

等資料。程式中的字面量,如直接書寫的100、“hello”和常量都是放在常量池中,常量池是方法區的一部分。

棧空間操作起來最快但是棧空間很小,通常大量的物件都放在堆空間,棧和堆的大小都是可以通過JVM的啟動引數進行調整。棧空間用完了會引發StackOverflowError,而堆和常量池空間不足會引發OutOfMemoryError。

String str = new String(“Hello”);

上面語句中變數str是一個引用,放在棧空間上的,用new創建出來的字串物件是放在堆空間上的,而“Hello”這個字面量是放在方法區的。棧空間中存放的是new物件在堆空間存放的地址值。

Person p = new Person(1);
Class Person{
int a=1;
public Person(int a){
this.a=a;
}
}

上面語句中p是一個指向Person類的例項(物件)的一個引用,放在棧空間中。真正建立物件的語句是new Person(1),這個物件放在堆空間中,對應棧空間p中存放的是這個Person物件在堆空間中的地址。其中a是成員變數,存放在堆空間中的。

若聲明瞭一個int型別的非成員變數a,然後對a進行賦值為1,因為a為基礎資料型別直接儲存在棧空間中,因此棧空間中a對應儲存的就是1。

注意

引用《java程式設計思想(第四版)》P23頁的一句話:

對於這些基本型別,java採取與C和C++相同的方法,也就是說,不用new來建立變數,而是建立一個並非是引用的“自動”變數。這個變數直接儲存“值”,並置於堆疊中,因此更加高效。

明白了上面關於物件和變數的儲存特點,現在我們來說說什麼是java值傳遞。

 值傳遞

2.1 什麼是值傳遞?

按值傳遞指的是在方法呼叫時,傳遞的引數是把值的拷貝傳遞過去的。由於傳遞的是值的拷貝,因此按值傳遞的特點就是傳遞過後兩個引數互不相干。

示例如下:

public class Test {
	public static void main(String[] args) {
		Test test=new Test();
		int a=1;
		test.sum(a);
		System.out.println("main方法裡的a="+a);
	}
public void sum(int a){
		a=2;
		System.out.println("sum方法裡的a="+a);
	}
}

執行結果:

sum方法裡的a=2
main方法裡的a=1


2.2 java值傳遞過程分析

(1)當執行到main方法第2行程式碼時:

首先在棧空間中建立一個變數為a,然後將1存入變數a所在記憶體空間中。

(2)當執行到sum方法中時:

main方法將a變數的拷貝傳給sum方法,此時在棧記憶體中會為sum方法中的變數a開闢一塊空間,存入由main方法傳過來的值1。通過a=2操作過後將sum方法中a的值改變成2。因此sum方法中的變數a更改不會影響main方法中的變數a。

 

圖(1)

引用傳遞

3.1  什麼是引用傳遞?

引用傳遞是C語言裡面的一種引數傳遞方式,就是在方法呼叫時,傳遞給另一個方法的是物件的引用,是同一個引用。但是在java語言中,並不存在像C語言這樣的引用傳遞,java語言中只存在值傳遞。之所以很多人把java傳遞物件誤以為是引用傳遞,是因為沒有深入的理解物件建立的記憶體分配規則。在java中傳遞物件,其實是傳遞的物件引用的值的拷貝,這個值也就是物件在堆記憶體中的地址。

Java中物件引用值的傳遞的特點是:兩個引用指向的是同一個物件,即同一個記憶體空間(試想,如果是引用傳遞,則就不會存在兩個引用了,只會有一個引用)。

示例程式碼如下:

public class Test {
	public static void main(String[] args) {
		Test test=new Test();
		Person p;
p=new Person();
		p.setAge(5);
		test.handle(p);
		System.out.println("main方法裡:"+p.toString());
	}
	public void handle(Person p){
		p.setAge(10);
		System.out.println("handle方法裡:"+p.toString());
	}
}

class Person{
	int age=0;
	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "Person [age=" + age + "]";
	}
}

執行結果:

handle方法裡:Person [age=10]
main方法裡:Person [age=10]


3.2  Java“引用傳遞”過程分析

(1)當執行完main方法第4行時:

此時程式在棧空間中開闢了一塊空間存放Person的一個引用p,並且在堆記憶體裡面建立了一個物件。

 

圖(2)

(2)當執行完main方法第5行的時:

此時main方法將引用p地址值的一個拷貝傳遞給handle方法,handl方法首先建立一個新的引用p,並且將傳過來的地址值賦值給新引用p。此時兩個引用都指向同一個記憶體空間。

 

圖(3)

(3)當執行完handle方法的第1行時:

此時handle方法中的引用p,修改了地址值為0000的Person物件的age=10,由於兩個引用指向的是同一個物件,因此main方法中Person物件的age也等於10,因為是同一個Person物件。

 

圖(4)

假如將handle方法修改成如下程式碼:

public void handle(Person p){
	p=new Person();
	p.setAge(10);
	System.out.println("handle方法裡:"+p.toString());
}

執行結果為:

handle方法裡:Person [age=10]
main方法裡:Person [age=5]


分析過程為:

執行到新handle方法第一行之前,執行過程和上面(1)(2)一樣,只不過當執行完第1行後放生了點變化。handle中重新new了一個Person物件,並且將handle方法中的引用p指向這個物件。

 

圖(5)

執行完handle方法第2行程式碼後,handle裡面的引用p修改了Person物件的age,此時由於main方法和handle方法中的引用指向了各自的Person物件,因此此次修改操作不會影響main方法中的Person物件,因此:main方法裡:Person [age=5]。

 

圖(6)

26.4 總結

(1)Java當中引數傳遞都是值傳遞!

(2)對於這些基本型別,java採取與C和C++相同的方法,也就是說,不用new來建立變數,而是建立一個並非是引用的“自動”變數。這個變數直接儲存“值”,並置於堆疊中,因此更加高效。

(3)對於大家理解的引用傳遞,其實傳遞的是引用的值,即傳遞引用物件的地址值。

注意:如有問題請批評指正!

【四川樂山程式設計師聯盟,歡迎大家加群相互交流學習5 7 1 8 1 4 7 4 3】