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】

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