1. 程式人生 > >java引用與引數傳遞

java引用與引數傳遞

網上都在講引數傳遞是一種拷貝,拷貝的變化不會影響原值,當然這是對的。還有一種說法,叫JAVA裡只有一種引數傳遞方式,值傳遞,這也是對的。但是我總覺得這些說法容易讓新人犯迷糊,因為有一個很常見的現象,很多時候拷貝也能改變原始物件的屬性。所以有些人又把引數傳遞跟基本型別傳遞分開去解釋這個問題,拆開顯然是不對的。
先來看這段非常簡單的程式碼:
package main;

public class Main {

	public static void main(String[] args) {
		TestObject a = new TestObject();
		TestObject b = new TestObject();
		System.out.println(a);  //列印結果為:[email protected]
		System.out.println(b);  //列印結果為:[email protected]
		System.out.println(a.equals(b)); //列印結果為:false
		System.out.println(b.testString);//列印結果為:no.0
		a = b;
		System.out.println(a); //列印結果為 :
[email protected]
System.out.println(b); //列印結果為 :[email protected] System.out.println(a.equals(b)); //列印結果為:true a.testString = "no.1"; System.out.println(b.testString); //列印結果為:no.1 } } package main; public class TestObject { public String testString = "no.0"; }
使用 TestObject a = new TestObject(); 的時候,這個a,事實上就是一個引用(可以理解為C語言中的指標,是一個指向堆中真實物件的地址)。 所以,很自然,如果a 和 b 兩個引用相同(指向同一個物件),執行a.testString = "no.1"之後,通過b獲得的物件也改變了。 接下來我們看引數傳遞:
package main;

public class Main {

	public static void main(String[] args) {
		// TODO 自動生成的方法存根
		TestObject a = new TestObject();
		TestObject b = new TestObject();
		System.out.println(a);  //列印結果為:
[email protected]
System.out.println(b); //列印結果為:[email protected] System.out.println(a.equals(b)); //列印結果為:false System.out.println(b.testString);//列印結果為:no.0 a = b; System.out.println(a); //列印結果為 :[email protected] System.out.println(b); //列印結果為 :[email protected] System.out.println(a.equals(b)); //列印結果為:true a.testString = "no.1"; System.out.println(b.testString); //列印結果為:no.1 //------------------------------------------------------ System.out.println(a);//列印結果為 :
[email protected]
change(a); //列印結果為 :[email protected] System.out.println(b.testString);//列印結果為 :no.2 } private static void change(TestObject s) { // TODO 自動生成的方法存根 System.out.println(s); s.testString = "no.2"; } } package main; public class TestObject { public String testString = "no.0"; }
我們都知道,s是a的拷貝。現在s變了,a也變了,這是為什麼?其實,s是一個引用,s,a,b指向了同一個物件。所以我們通過s改變這個物件的時候,通過a,b,s獲取到的物件都會改變。所以,JAVA裡面通過引數傳遞,去改變物件是“可行的”,當然這個可行要打一個引號,因為我們不推薦這麼做。
再來看這樣的引數傳遞:
package main;

public class Main {

	public static void main(String[] args) {
		// TODO 自動生成的方法存根
		TestObject a = new TestObject();
		TestObject b = new TestObject();
		System.out.println(a);  //列印結果為:[email protected]
		System.out.println(b);  //列印結果為:[email protected]
		System.out.println(a.equals(b)); //列印結果為:false
		System.out.println(b.testString);//列印結果為:no.0
		a = b;
		System.out.println(a); //列印結果為 :[email protected]
		System.out.println(b); //列印結果為 :[email protected]
		System.out.println(a.equals(b)); //列印結果為:true
		
		a.testString = "no.1";
		System.out.println(b.testString); //列印結果為:no.1
		//------------------------------------------------------
		
		System.out.println(a);//列印結果為 :[email protected]
		change(a);            //列印結果為 :[email protected]
		System.out.println(b.testString);//列印結果為 :no.2
		
		//------------------------------------------------------
		System.out.println(a); //列印結果為 :[email protected]
		change2(a);
		System.out.println(a); //列印結果為 :[email protected]
		System.out.println(b.testString);//列印結果為 :no.3
		
	}

	private static void change(TestObject s) {
		System.out.println(s); //列印結果為 :[email protected]
		s.testString = "no.2";
	}
	
	private static void change2(TestObject s) {
		System.out.println(s);//列印結果為 :[email protected]
		s.testString = "no.3";
		TestObject c = new TestObject();
		s = c;
		System.out.println(s);//列印結果為 :[email protected]
		s.testString = "no.4";
		System.out.println(s.testString);//列印結果為 :no.4
	}
}



package main;

public class TestObject {
	public String testString = "no.0";
}


看結果,最終,s改變了嗎?s改變了,s變成了[email protected]。那a變了嗎?a沒有變,a還是[email protected]。s是不是拷貝?當然是拷貝,s變了,但是a沒有改變。 其實,當s是[email protected]的時候,通過s.testString = "no.3";改變s指向的物件的屬性的時候,a,b,s由於指向了同一個物件,所以看起來a也變了。當s變為[email protected]的時候,由於s和a,b已經不再指向同一個物件了,所以,s變了,a,b指向的物件不會有改變。 基本型別就不講了,網上都是講基本型別的引數傳遞拷貝不變性的。總結一下: 1.引數傳遞是拷貝,沒問題。 2.JAVA裡面的傳遞只有一種,值傳遞,沒問題。
3.通過引用去改變堆中物件屬性的時候,只要通過一個引用改變指向物件的屬性,指向同一個物件的所有引用,看起來都會變。所以有時候看起來,方法內和方法外的引用一起改變了。 所以,在JAVA編寫過程中,儘量不要使用public屬性,而是要使用get,set方法。因為如果在你的函式中,通過引用改變了一些類的屬性,就會使得程式變得混亂而不可讀。為了後期維護的方便,請不要直接在呼叫的方法中通過引用改變引用指向物件的屬性。 於此同時,克隆也是一樣的道理,淺克隆中,克隆的物件如果存在引用,將會使得調用出現異常,因為兩個克隆物件中如果擁有同一個引用屬性,當你呼叫這個引用的時候,就會出現克隆的屬性一起改變的情況,克隆的兩個物件確實是兩個物件,但是他們的引用屬性卻指向了同一個物件。如果他們指向的物件無法被修改,那麼淺克隆完全是符合要求的。但是如果要通過這個引用去修改指向的那個物件,這其實是一定程度上違反了JAVA的基本原則(對擴充套件開放,對修改關閉)。