天天看點

java8程式設計思想-指派(對象傳遞和傳回)(值傳遞和引用傳遞)On Java 8

文章目錄

  • On Java 8
    • 第四章 運算符-指派
    • 附錄: 對象傳遞和傳回
      • 傳遞引用
      • 本地拷貝
      • 控制克隆
      • 不可變類
      • 本章小結

On Java 8

《On Java 8》中的内容

指派

對象傳遞和傳回

了解:值傳遞和引用傳遞

第四章 運算符-指派

指派

​ 運算符的指派是由符号 = 完成的。它代表着擷取 = 右邊的值并賦給左邊的變量。右邊可以是任何常量、變量或者可産生一個傳回值的表達式。但左邊必須是一個明确的、已命名的變量。也就是說,必須要有一個實體的空間來存放右邊的值。舉個例子來說,可将一個常數賦給一個變量(A = 4),但不可将任何東西賦給一個常數(比如不能 4 =A)。

​ (值傳遞和引用傳遞:)基本類型的指派都是直接的,而不像對象,賦予的隻是其記憶體的引用。舉個例子,a = b ,如果 b 是基本類型,那麼指派操作會将 b 的值複制一份給變量 a,此後若 a 的值發生改變是不會影響到 b 的。作為一名程式員,這應該成為我們的常識。

​ 如果是為對象指派,那麼結果就不一樣了。對一個對象進行操作時,我們實際上操作的是它的引用。是以我們将右邊的對象賦予給左邊時,賦予的隻是該對象的引用。此時,兩者指向的堆中的對象還是同一個。代碼示例:

// operators/Assignment.java
// Assignment with objects is a bit tricky*

class Tank {
	int level;
}

public class Assignment {

	public static void main(String[] args) {
		Tank t1 = **new** Tank();
		Tank t2 = **new** Tank();
    t1.level = 9;
    t2.level = 47;
    System.out.println("1: t1.level: " + t1.level +", t2.level: " + t2.level);
    
    t1 = t2;
    System.out.println("2: t1.level: " + t1.level +", t2.level: " + t2.level);
   
    t1.level = 27;
    System.out.println("3: t1.level: " + t1.level +", t2.level: " + t2.level);
	} 
}
           

輸出結果:

1: t1.level: 9, t2.level: 47

2: t1.level: 47, t2.level: 47

3: t1.level: 27, t2.level: 27

​ 這是一個簡單的 Tank 類,在 main() 方法建立了兩個執行個體對象。兩個對象的 level屬性分别被賦予不同的值。然後,t2 的值被賦予給 t1。在許多程式設計語言裡,預期的結果是 t1 和 t2 的值會一直相對獨立。但是,在 Java 中,由于賦予的隻是對象的引用,改變 t1 也就改變了 t2。這是因為 t1 和 t2 此時指向的是堆中同一個對象(引用傳遞)。(t1 原始對象的引用在 t2 指派給其時丢失,它引用的對象會在垃圾回收時被清理)。

​ 這種現象通常稱為别名(aliasing),這是 Java 處理對象的一種基本方式。但是假若你不想出現這裡的别名引起混淆的話,你可以這麼做。代碼示例:

​ 較之前的做法,這樣做保留了兩個單獨的對象,而不是丢棄一個并将 t1 和 t2 綁定到同一個對象。但是這樣的操作有點違背 Java 的設計原則。對象的指派是個需要重視的環節,否則你可能收獲意外的 “驚喜”。

// 方法調用中的别名現象
//當我們把對象傳遞給方法時,會發生别名現象。

// operators/PassObject.java
// 正在傳遞的對象可能不是你之前使用的

class Letter {
	char c;
}

public class PassObject {
	static void f(Letter y) {
		y.c = 'z'; 
	}

  public static void main(String[] args) {
    Letter x = new Letter();
    x.c = 'a';
    System.out.println("1: x.c: " + x.c);	
    f(x);
    System.out.println("2: x.c: " + x.c);
  } 
}
           

輸出結果:

1: x.c: a

2: x.c: z

​ 在許多程式設計語言中,方法 f() 似乎會在内部複制其參數 Letter y。但是一旦傳遞了一個引用,那麼實際上 y.c =‘z’; 是在方法 f() 之外改變對象。别名現象以及其解決方案是個複雜的問題,在附錄中有包含:對象傳遞和傳回。意識到這一點,我們可以警惕類似的陷阱。

附錄: 對象傳遞和傳回

​ 到現在為止,你已經對 “傳遞” 對象實際上是傳遞引用這一想法想法感到滿意。

​ 在許多程式設計語言中,你可以使用該語言的 “正常” 方式來傳遞對象,并且大多數情況下一切正常。但是通常會出現這種情況,你必須做一些不平常的事情,突然事情變得更加複雜。Java 也不例外,當您傳遞對象并對其進行操作時,準确了解正在發生的事情很重要。本附錄提供了這種見解。

​ 提出本附錄問題的另一種方法是,如果你之前使用類似 C++ 的程式設計語言,則是 “Java 是否有指針?” Java 中的每個對象辨別符(除原語外)都是這些指針之一,但它們的用法是不僅受編譯器的限制,而且受運作時系統的限制。換一種說法,Java 有指針,但沒有指針算法。這些就是我一直所說的 “引用”,您可以将它們視為 “安全指針”,與國小的安全剪刀不同-它們不敏銳,是以您不費吹灰之力就無法傷害自己,但是它們有時可能很乏味。

傳遞引用

當你将引用傳遞給方法時,它仍指向同一對象。一個簡單的實驗示範了這一點:

// references/PassReferences.java

public class PassReferences {
	public static void f(PassReferences h) {
		System.out.println("h inside f(): " + h);
	}

  public static void main(String[] args) {
    PassReferences p = new PassReferences();
    System.out.println("p inside main(): " + p);
    f(p);
  } 
}

/* Output:
p inside main(): [email protected]
h inside f(): [email protected]42
*/
           

​ 方法 toString() 在列印語句中自動調用,并且 PassReferences 直接從 Object繼承而無需重新定義 toString()。是以,使用的是 Object 的 toString()版本,它列印出對象的類,然後列印出該對象所在的位址(不是引用,而是實際的對象存儲)。

本地拷貝

控制克隆

不可變類

本章小結