夜光:Java成神之路(二)擅長的語言

夜光序言:

 

命中註定的人,並不是註定與之相愛、結合、交配的人。
命中註定的人,是爲了令你成爲你註定成爲的樣子而出現的人。

 

 
 
正文:
 
                                              以道御術 / 以術識道

 



 

3.3.2 深拷貝和淺拷貝

 
上面的示例代碼中,Person 中有兩個成員變量,分別是 name 和 age, name 是 String 類型, age 是 int 類型。代碼非常簡單,如下所示:
 
public class Person implements Cloneable{
   privat int age ;
   private String name;
 public Person(int age, String name) {
  this.age = age;
  this.name = name;
  }
 public Person() {}
 public int getAge() {
  return age;
  }
  public String getName() {
return name;
  }
  @Override
  protected Object clone() throws CloneNotSupportedException {
  return (Person)super.clone();
 }
} 

 

 
 
 
由於 age 是基本數據類型,那麼對它的拷貝沒有什麼疑議,直接將一個 4 字節的整數值拷貝過來就行。
 
但是 name 是 String 類型的, 它只是一個引用, 指向一個真正的 String 對象,那麼對它的拷貝有兩種方式:
直接將原對象中的 name 的引用值拷貝給新對象的 name 字段,
或者是根據原 Person 對象中的 name 指向的字符串對象創建一個新的相同的字符串對象,將這個新字符串對象的引用賦給新拷貝的 Person 對象的 name 字段。

 

 

這兩種拷貝方式分別叫做淺拷貝和深拷貝。深拷貝和淺拷貝的原理如下圖所示:

下面通過代碼進行驗證。

 
如果兩個 Person 對象的 name 的地址值相同, 說明兩個對象的 name 都指向同一個 String 對象,也就是淺拷貝, 而如果兩個對象的 name 的地址值不同, 那麼就說明指向不同的 String 對象, 也就是在拷貝 Person 對象的時候, 同時拷貝了 name 引用的 String 對象, 也就是深拷貝。
 

驗證代碼如下:

 
 Person p = new Person(23, "zhang");
 Person p1 = (Person) p.clone();
 String result = p.getName() == p1.getName() ? "clone 是淺拷貝的" : "clone 是深拷貝的";
 System.out.println(result); 

 

 
 

打印結果爲:


clone 是淺拷貝的 

 

 
所以,clone 方法執行的是淺拷貝, 在編寫程序時要注意這個細節。

 

如何進行深拷貝:

由上一節的內容可以得出如下結論:如果想要深拷貝一個對象,這個對象必須要實現 Cloneable 接口,實現 clone方法,並且在 clone 方法內部,把該對象引用的其他對象也要 clone 一份,這就要求這個被引用的對象必須也要實現 Cloneable 接口並且實現 clone 方法。
 
 
那麼,按照上面的結論,實現以下代碼 Body 類組合了 Head 類,要想深拷貝Body 類,必須在 Body 類的 clone 方法中將 Head 類也要拷貝一份。
 

 

代碼如下:

1.static class Body implements Cloneable{
2. public Head head;
3. public Body() {}
4. public Body(Head head) {this.head = head;}
5. @Override
6. protected Object clone() throws CloneNotSupportedException {
7. Body newBody = (Body) super.clone();
8. newBody.head = (Head) head.clone();
9. return newBody;
10. }
11.}
12.static class Head implements Cloneable{
13. public Face face;
14. public Head() {}
15. @Override
16. protected Object clone() throws CloneNotSupportedException {
17. return super.clone();
18. } }
19.public static void main(String[] args) throws CloneNotSupportedException {
20. Body body = new Body(new Head(new Face()));
21. Body body1 = (Body) body.clone();
22. System.out.println("body == body1 : " + (body == body1) );
23. System.out.println("body.head == body1.head : " + (body.head == body1.head));
24.} 

 

打印結果爲:


1. body == body1 : false
2. body.head == body1.head : false

 

 

二、JavaSE 語法

 

1. Java 有沒有 goto 語句?

goto 是 Java 中的保留字,在目前版本的 Java 中沒有使用。
 
根據 James Gosling(Java 之父)編寫的《The Java Programming Language》一書的附錄中給出了一個 Java 關鍵字列表
 
 
其中有 goto 和 const,但是這兩個是目前無法使用的關鍵字,因此有些地方將其稱之爲保留字,其實保留字這個詞應該有更廣泛的意義,因爲熟悉 C 語言的程序員都知道,在系統類庫中使用過的有特殊意義的單詞或單詞的組合都被視爲保留字

 


 

2. & 和 && 的區別

 

&運算符有兩種用法:(1)按位與;(2)邏輯與。
 
&&運算符是短路與運算。邏輯與跟短路與的差別是非常巨大的,雖然二者都要求運算符左右兩端的布爾值都是true 整個表達式的值纔是 true。
 
&&之所以稱爲短路運算是因爲,如果&&左邊的表達式的值是 false,右邊的表達式會被直接短路掉,不會進行運算。
 
 
 
很多時候我們可能都需要用&&而不是&,例如在驗證用戶登錄時判定用戶名不是 null 而且不是空字符串,應當寫爲 username != null &&!username.equals(""),二者的順序不能交換,更不能用&運算符
 
 
因爲第一個條件如果不成立,根本不能進行字符串的 equals 比較,否則會產生 NullPointerException 異常。

 

注意:邏輯或運算符(|) 和短路或運算符(||)的差別也是如此。


 

 

3. 在 Java 中,如何跳出當前的多重嵌套循環

 

在最外層循環前加一個標記如 A,然後用 break A;可以跳出多重循環。

 
(Java 中支持帶標籤的 break 和 continue語句,作用有點類似於 C 和 C++中的 goto 語句
 
但是就像要避免使用 goto 一樣,應該避免使用帶標籤的 break 和 continue
 
因爲它不會讓你的程序變得更優雅,很多時候甚至有相反的作用)
 
 

 

4. 兩個對象值相同 (x.equals(y) == true) ,但卻可有不同的 hashCode,這句話對不對?

不對,如果兩個對象 x 和 y 滿足 x.equals(y) == true,它們的哈希碼(hashCode)應當相同。
 
Java 對於 eqauls 方法和 hashCode 方法是這樣規定的:
 
(1)如果兩個對象相同(equals 方法返回 true),那麼它們的 hashCode 值一定要相同;
 
(2)如果兩個對象的 hashCode 相同,它們並不一定相同。
 
 
當然,你未必要按照要求去做,但是如果你違背了上述原則就會發現在使用容器時,相同的對象可以出現在 Set 集合中,同時增加新元素的效率會大大下降(對於使用哈希存儲的系統,如果哈希碼頻繁的衝突將會造成存取性能急劇下降)。
 
 
關於 equals 和 hashCode 方法,很多 Java 程序員都知道,但很多人也就是僅僅知道而已,在 Joshua Bloch 的大作《Effective Java》(很多軟件公司,《Effective Java》、《Java 編程思想》以及《重構:改善既有代碼質量》

夜光:

是 Java 程序員必看書籍,如果你還沒看過,那就趕緊去看電子書或者買............,中是這樣介紹 equals 方法的。

 
首先 equals 方法必須滿足自反性(x.equals(x)必須返回 true)、對稱性(x.equals(y)返回 true 時,y.equals(x)也必須返回 true)、傳遞性(x.equals(y)和 y.equals(z)都返回 true 時,x.equals(z)也必須返回 true)和一致性(當 x 和 y 引用的對象信息沒有被修改時,多次調用 x.equals(y)應該得到同樣的返回值),而且對於任何非 null 值的引用 x,x.equals(null)必須返回 false。
 
 

實現高質量的 equals 方法的訣竅包括:

 

1. 使用==操作符檢查"參數是否爲這個對象的引用";
 
2. 使用 instanceof 操作符檢查"參數是否爲正確的類型";
 
3. 對於類中的關鍵屬性,檢查參數傳入對象的屬性是否與之相匹配;
 
4. 編寫完 equals 方法後,問自己它是否滿足對稱性、傳遞性、一致性;
 
5. 重寫 equals 時總是要重寫 hashCode;
 
6. 不要將 equals 方法參數中的 Object 對象替換爲其他的類型,在重寫時不要忘掉 @Override 註解。

 


 

5. 是否可以繼承 String

 

String 類是 final 類,不可以被繼承

繼承 String 本身就是一個錯誤的行爲,對 String 類型最好的重用方式是關聯關係(Has-A)和依賴關係(Use-A)而不是繼承關係(Is-A)

 


 

 

6. 當一個對象被當作參數傳遞到一個方法後,此方法可改變這個對象的屬性,並可返回變化後的結果,那麼這裏到底是值傳遞還是引用傳遞?

 

是值傳遞。

 
Java 語言的方法調用只支持參數的值傳遞。當一個對象實例作爲一個參數被傳遞到方法中時,參數的值就是對該對象的引用。
 
對象的屬性可以在被調用過程中被改變,但對對象引用的改變是不會影響到調用者的。
 
C++ 和 C#中可以通過傳引用或傳輸出參數來改變傳入的參數的值。
 
說明:Java 中沒有傳引用實在是非常的不方便,這一點在 Java 8 中仍然沒有得到改進,正是如此在 Java 編寫的代碼中才會出現大量的 Wrapper 類(將需要通過方法調用修改的引用置於一個 Wrapper 類中,再將 Wrapper 對象傳入方法)
 

 

這樣的做法只會讓代碼變得臃腫,尤其是讓從 C 和 C++轉型爲 Java 程序員的開發者無法容忍。

 

 


 

7. 重載(overload)和重寫(override)的區別?重載的方法能否根據返回類型進行區分?

 

方法的重載和重寫都是實現多態的方式,區別在於前者實現的是編譯時的多態性,而後者實現的是運行時的多態性。
 
 
重載發生在一個類中,同名的方法如果有不同的參數列表(參數類型不同、參數個數不同或者二者都不同)則視爲重載;
 
重寫發生在子類與父類之間,重寫要求子類被重寫方法與父類被重寫方法有相同的返回類型,比父類被重寫方法更好訪問,不能比父類被重寫方法聲明更多的異常(里氏代換原則)。
 
重載對返回類型沒有特殊的要求。
 

方法重載的規則:

 
1.方法名一致,參數列表中參數的順序,類型,個數不同。
 
2.重載與方法的返回值無關,存在於父類和子類,同類中。
 
3.可以拋出不同的異常,可以有不同修飾符。
 
 
 
 
 

 

 

 

 

 

 

 

 

 

 

 

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