<font size="3">html
本文內容來自MIT_6.031_sp18: Software Construction課程的Readings部分,採用CC BY-SA 4.0協議。java
因爲咱們學校(哈工大)大二軟件構造課程的大部分素材取自此,也是推薦的閱讀材料之一,因而打算作一些翻譯工做,本身學習的同時也能幫到一些懶得看英文的朋友。另外,該課程的閱讀資料中有的練習題沒有標準答案,所給出的「正確答案」爲譯者所寫,有錯誤的地方還請指出。程序員
(更新:從第10章開始只翻譯正確答案)web
<br />編程
<br />api
譯者:李秋豪數組
審校:安全
V1.0 Thu Apr 12 21:02:06 CST 2018數據結構
<br />oracle
<br />
在以前的閱讀材料中,咱們已經描述了抽象數據類型(ADT)是由它對應的操做而非內部表示決定的。而ADT中的抽象函數解釋了該類型是如何將內部表示映射爲使用者理解的抽象數據的,咱們也看到了抽象函數決定了咱們應該如何實現ADT的各個操做。
在這篇閱讀中咱們會聚焦於如何定義ADT的相等:抽象函數會給咱們對相等操做一個清晰的定義。
在現實物理世界中,任何對象都是不相等的——在某些層次,即便是兩片雪花也是不一樣的,即便這種不一樣只是在空間中的位置(嚴格一點的話,在原子層次不能這麼說,不過對於現實生活中「大」的對象已經足夠正確了)。因此任何物理對象都不會真正相等,它們只會在某一些方面類似。
可是對於人類語言,或者對於數學世界,你能夠有不少徹底相同的東西。例若有兩個相等的表達式是很正常的,又例如√9 和 3表現了徹底相同的數值。
<br />
嚴格來講,咱們能夠從三個角度定義相等:
**抽象函數:**回憶一下抽象函數(AF: R → A ),它將具體的表示數據映射到了抽象的值。若是AF(a)=AF(b),咱們就說a和b相等。
**等價關係:**等價是指對於關係E ⊆ T x T ,它知足:
咱們說a等於b當且僅當E(a,b)。
以上兩種角度/定義其實是同樣的,經過等價關係咱們能夠構建一個抽象函數(譯者注:就是一個封閉的二元關係運算);而抽象函數也能推出一個等價關係。
第三種斷定抽象值相等的方法是從使用者/外部的角度去觀察。
**觀察:**咱們說兩個對象相等,當且僅當使用者沒法觀察到它們之間有不一樣,即每個觀察總會都會獲得相同的結果。例如對於兩個集合對象 {1,2} 和 {2,1},咱們就沒法觀察到不一樣:
從ADT來講,「觀察」就意味着使用它的觀察者/操做。因此咱們也能夠說兩個對象相等當且僅當它們的全部觀察操做都返回相同的結果。
這裏要注意一點,**「觀察者/操做」都必須是ADT的規格說明中規定好的。**Java容許使用者跨過抽象層次去觀察對象的不一樣之處。例如==
就可以判斷兩個變量是不是索引到同一個存儲地方的,而 System.identityHashCode()
則是根據存儲位置計算返回值的。可是這些操做都不是ADT規格說明中的操做,因此咱們不能根據這些「觀察」去判斷兩個對象是否相等。
這裏有一個不可變ADT的例子:
public class Duration { private final int mins; private final int secs; // Rep invariant: // mins >= 0, secs >= 0 // Abstraction function: // AF(min, secs) = the span of time of mins minutes and secs seconds /** Make a duration lasting for m minutes and s seconds. */ public Duration(int m, int s) { mins = m; secs = s; } /** @return length of this duration in seconds */ public long getLength() { return mins*60 + secs; } }
那麼下面哪一些變量/對象應該被認爲是相等的呢?
Duration d1 = new Duration (1, 2); Duration d2 = new Duration (1, 3); Duration d3 = new Duration (0, 62); Duration d4 = new Duration (1, 2);
試着分別從抽象函數、等價關係以及使用者觀察這三個角度分析。
Any second now
思考上面的 Duration
以及變量 d1
, d2
, d3
, d4
,從抽象函數或等價關係來看,哪一些選項和d1
相等?
[x] d1
[ ] d2
[x] d3
[x] d4
Eye on the clock
從使用者觀察的角度,哪一些選項和d1
相等?
[x] d1
[ ] d2
[x] d3
[x] d4
<br />
和不少其餘語言同樣,Java有兩種判斷相等的操做—— ==
和 equals()
。
==
比較的是索引。更準確的說,它測試的是指向相等(referential equality)。若是兩個索引指向同一塊存儲區域,那它們就是==的。對於咱們以前提到過的快照圖來講,==
就意味着它們的箭頭指向同一個對象。equals()
操做比較的是對象的內容,換句話說,它測試的是對象值相等(object equality)。e在每個ADT中,quals操做必須合理定義。做爲對比,這裏列出來了幾個語言中的相等操做:
referential equality | object equality | |
---|---|---|
Java | == |
equals() |
Objective C | == |
isEqual: |
C# | == |
Equals() |
Python | is |
== |
Javascript | == |
n/a |
注意到==
在Java和Python中的意義正好相反,別被這個弄混了。
做爲程序員,咱們不能改變測試指向相等操做的意義。在Java中,==
老是判斷指向是否相等。可是當咱們定義了一個新的ADT,咱們就須要判斷對於這個ADT來講對象值相等意味着什麼,即如何判斷對象值相等/如何實現equals()
操做。
<br />
equals()
是在 Object
中定義的,它的(默認)實現方式以下:
public class Object { ... public boolean equals(Object that) { return this == that; } }
能夠看到, equals()
在Object
中的實現方法就是測試指向/索引相等。對於不可變類型的對象來講,這幾乎老是錯的。因此你須要覆蓋(override) equals()
方法,將其替換爲你的實現。
咱們來看一個例子,Duration
的相等操做:
public class Duration { ... // Problematic definition of equals() public boolean equals(Duration that) { return this.getLength() == that.getLength(); } }
運行下面的測試代碼:
Duration d1 = new Duration (1, 2); Duration d2 = new Duration (1, 2); Object o2 = d2; d1.equals(d2) → true d1.equals(o2) → false
以下圖所示,能夠看到,雖然d2
和o2
最終指向的是同一個對象/存儲區域,可是咱們的 equals()
卻獲得的不一樣的結果。
這是怎麼回事呢?事實上, Duration
只是重載(overloaded)了 equals()
方法,由於它的方法標識和Object
中的不同,也就是說,這是 Duration
中有兩個 equals()
方法:一個是從 Object
隱式繼承下來的equals(Object)
,還有一個就是咱們寫的 equals(Duration)
。
public class Duration extends Object { // explicit method that we declared: public boolean equals(Duration that) { return this.getLength() == that.getLength(); } // implicit method inherited from Object: public boolean equals(Object that) { return this == that; } }
咱們在以前的「靜態檢查」閱讀中已經說太重載了,回憶一下,編譯器會在重載操做之間根據參數類型作出選擇。例如,當你使用/
操做符的時候,編譯器會根據參數是ints仍是floats選擇整數除法或浮點數觸發。同理,若是咱們對equals()
傳入的是 Duration
索引,編譯器就會選擇equals(Duration)
這個操做。這樣,相等性就變得不肯定了。
這是一個很容易犯的錯誤,即由於方法標識的緣由重載而不是覆蓋了的方法。在Java中,你可使用 @Override
來提示編譯器你是要後面的方法覆蓋父類中的方法,而編譯器會自動檢查這個方法是否和父類中的方法有着相同的標識(產生覆蓋),不然編譯器會報錯。
如今咱們更正 Duration
的 equals()
:
@Override public boolean equals(Object that) { return that instanceof Duration && this.sameValue((Duration)that); } // returns true iff this and that represent the same abstract value private boolean sameValue(Duration that) { return this.getLength() == that.getLength(); }
它首先測試了傳入的that
是 Duration
(譯者注:這裏that
還能夠是 Duration
的子類),而後調用sameValue()
去判斷它們的值是否相等。表達式 (Duration)that
是一個類型轉換操做,它告訴編譯器你確信 that
指向的是一個 Duration
對象。
咱們再次運行測試代碼,結果正確:
Duration d1 = new Duration(1, 2); Duration d2 = new Duration(1, 2); Object o2 = d2; d1.equals(d2) → true d1.equals(o2) → true
instanceof
操做符 是用來測試一個實例是否屬於特定的類型。 instanceof
是動態檢查而非咱們更喜歡的靜態檢查。廣泛來講,在面向對象編程中使用 instanceof
是一個很差的選擇。在本門課程中——在不少Java編程中也是這樣——**除了實現相等操做,instanceof
不能被使用。**這也包括其餘在運行時肯定對象類型的操做,例如 getClass
。
咱們會在之後學習如何使用更安全、可改動的代碼而不是 instanceof
。
譯者注:關於在equals()
中使用 getClass
仍是 instanceof
操做符存在一些爭議,焦點集中於使用 instanceof
操做符可能會影響相等的對稱性(父子類)。《Java核心技術 卷一 第十版》的5.2.2節對此作了說明,讀者能夠參考一下。
<br />
因爲Object
的規格說明實在過重要了,咱們有時也稱它爲「對象契約」(the Object Contract)。你能夠在object
類中找到這些規格說明。咱們在這裏主要研究equals
的規格說明。當你在覆蓋equals
時,要記得遵照這些規定:
equals
必須定義一個等價關係。即一個知足自反性、對稱性和傳遞性關係。equals
必須是肯定的。即連續重複的進行相等操做,結果應該相同。x
, x.equals(null)
應該返回false。equals
操做後結果爲真,那麼它們各自的hashCode
操做的結果也應該相同。正如前面所說,equals()
操做必須構建出一個知足自反性、對稱性、傳遞性的等價關係。若是沒有知足,那麼與相等相關的操做(例如集合、搜索)將變得不可預測。例如你確定不但願a
等於b
可是後來發現b
不等於a
,這都是很是隱祕的bug。
這裏舉出了一個例子,它試圖將相等變得更復雜,結果致使了錯誤。假設咱們但願在判斷 Duration
相等的時候容許一些偏差,由於不一樣的電腦同步的時間可能會有一小點不一樣:
@Override public boolean equals(Object that) { return that instanceof Duration && this.sameValue((Duration)that); } private static final int CLOCK_SKEW = 5; // seconds // returns true iff this and that represent the same abstract value within a clock-skew tolerance private boolean sameValue(Duration that) { return Math.abs(this.getLength() - that.getLength()) <= CLOCK_SKEW; }
上面相等操做違背了等價關係裏面的什麼屬性?
Equals-ish
思考上面提到的 Duration
:
public class Duration { private final int mins; private final int secs; // Rep invariant: // mins >= 0, secs >= 0 // Abstraction function: // AF(min, secs) = the span of time of mins minutes and secs seconds /** Make a duration lasting for m minutes and s seconds. */ public Duration(int m, int s) { mins = m; secs = s; } /** @return length of this duration in seconds */ public long getLength() { return mins*60 + secs; } @Override public boolean equals(Object that) { return that instanceof Duration && this.sameValue((Duration)that); } private static final int CLOCK_SKEW = 5; // seconds // returns true iff this and that represent the same abstract value within a clock-skew tolerance private boolean sameValue(Duration that) { return Math.abs(this.getLength() - that.getLength()) <= CLOCK_SKEW; } }
假設下面這些 Duration
對象被建立:
Duration d_0_60 = new Duration(0, 60); Duration d_1_00 = new Duration(1, 0); Duration d_0_57 = new Duration(0, 57); Duration d_1_03 = new Duration(1, 3);
如下哪一些選項會返回真?
[x] d_0_60.equals(d_1_00)
[x] d_1_00.equals(d_0_60)
[x] d_1_00.equals(d_1_00)
[x] d_0_57.equals(d_1_00)
[ ] d_0_57.equals(d_1_03)
[x] d_0_60.equals(d_1_03)
Skewed up
上面相等操做違背了等價關係裏面的什麼屬性?(忽略null索引)
[ ] recursivity
[ ] 自反性
[ ] sensitivity
[ ] 對稱性
[x] 傳遞性
Buggy equality
若是你想證實上面的equals
違反了自反性,你須要建立幾個對象?
[ ] none
[x] 1 object
[ ] 2 objects
[ ] 3 objects
[ ] all the objects in the type
Null, null, null
和咱們以前說過的不一樣,equals
操做容許參數爲null
,這是由於Object
的規格說明中提到了這種前置條件:
x
, x.equals(null)
應該返回false若是 x.equals(null)
返回true,equals
將會違背等價的什麼屬性?
[ ] recursivity
[ ] 自反性
[ ] sensitivity
[x] 對稱性
[ ] 傳遞性
哪一行代碼會讓 equals()
在 that
是null時返回false?
1 @Override 2 public boolean equals(Object that) { 3 return that instanceof Duration 4 && this.sameValue((Duration)that); } // returns true iff this and that represent the same abstract value 5 private boolean sameValue(Duration that) { 6 return this.getLength() == that.getLength(); }
--> 3
爲了理解契約中有關hashCode
的部分,你須要對哈希表的工做原理有必定的瞭解。兩個常見的聚合類型 HashSet
和 HashMap
就用到了哈希表的數據結構,而且依賴hashCode
保存集合中的對象以及產生合適的鍵(key)。
一個哈希表表示的是一種映射:從鍵值映射到值的抽象數據類型。哈希表提供了常數級別的查找,因此它一般比數或者列表的性能要好。鍵不必定是有序的,也不必定有什麼特別的屬性,除了類型必須提供 equals
和 hashCode
兩個方法。
哈希表是怎麼工做的呢?它包含了一個初始化的數組,其大小是咱們設計好的。當一個鍵值對準備插入時,咱們經過hashcode計算這個鍵,產生一個索引,它在咱們數組大小的範圍內(例如取模運算)。最後咱們將值插入到數組索引對應的位置。
哈希表的一個基本不變量就是鍵必須在hashcode規定的範圍內。
Hashcode最好被設計爲鍵計算後的索引應該平滑、均勻的分佈在全部範圍內。可是偶爾衝突也會發生,例如兩個鍵計算出了一樣的索引。所以哈希表一般存儲的是一個鍵值對的列表而非一個單個的值,這一般被稱爲哈希桶(hash bucket)。而在Java中,鍵值對就是一個有着兩個域的對象。當插入時,你只要像計算出的索引位置插入一個鍵值對。當查找時,你先根據鍵哈希出對應的索引,而後在索引對應的位置找到鍵值對列表,最後在這個列表中查找你的鍵。
如今你應該知道了爲何Object
的規格說明要求相等的對象必須有一樣的hashcode。若是兩個相等的對象hashcode不一樣,那麼它們在聚合類存儲的時候位置也就不同——若是你存入了一個對象,而後查找一個相等的對象,就可能在錯誤的索引處進行查找,也就會獲得錯誤的結果。
Object
默認的 hashCode()
實現和默認的 equals()
保持一致:
public class Object { ... public boolean equals(Object that) { return this == that; } public int hashCode() { return /* the memory address of this */; } }
對於索引a
和b
,若是 a == b
,那麼a和b的存儲地址也就相同,hashCode()
的結果也就相同。因此Object
的契約知足。
可是對於不可變對象來講,它們須要從新實現hashCode()
。例如上面提到的 Duration
,由於咱們尚未覆蓋默認的 hashCode()
,實際上打破了對象契約:
Duration d1 = new Duration(1, 2); Duration d2 = new Duration(1, 2); d1.equals(d2) → true d1.hashCode() → 2392 d2.hashCode() → 4823
d1
和 d2
是 equals()
爲真的,可是它們的hashcode不同,因此咱們須要修復它。
一個簡單粗暴的解決辦法就是讓hashCode
老是返回相同的常量,這樣每個對象的hashcode就都同樣了。這樣確實知足了對象契約,可是會給性能帶來災難性的後果,由於咱們必須將每個鍵值對都保存到相同的位置,並且查找會是線性遍歷全部插入過的對象。
而一個廣泛(更合理)的方法就是計算對象每個內容的hashcode而後對它們進行一系列算術運算,最終返回一個綜合hashcode。對於 Duration
而言就更簡單了,由於它只有一個整型內容:
@Override public int hashCode() { return (int) getLength(); }
更多有關於hashcode的細節,你能夠參考Josh Bloch的書 Effective Java,他詳細介紹了hashcode應該注意的問題和設計方法。另外StackOverflow上面也有關於這個的問答。在近些版本的Java中,你能夠利用 Objects.hash()
方便的計算多個域的綜合hashcode。
要注意的是,只要你知足了相等的對象產生相同的hashcode,無論你的hashcode是如何實現的,你的代碼都會是正確的。哈希碰撞僅僅只會性能,而一個錯誤哈希方法則會帶來錯誤!
最重要的是,若是你沒有覆蓋默認的hashCode
,你就會繼承Object
中根據存儲地址得到的hashCode
。若是你又覆蓋了equals
,這就意味着你很大可能破壞了對象契約,因此一個通用準則就是:
當你覆蓋
equals
後,將hashCode
也覆蓋
在不少年前,一個本課程的學生花了幾個小時找到了一個bug:他將 hashCode
拼成了 hashcode
,也就是說他沒有將默認的 hashCode
覆蓋,最終奇怪的事情就發生了。因此記得使用 @Override
!
Give me the code
思考下面這個ADT:
class Person { private String firstName; private String lastName; ... @Override public boolean equals(Object that) { return that instanceof Person && this.sameValue(that); } // returns true iff this and that represent the same abstract value private boolean sameValue(Person that) { return this.lastName.toUpperCase().equals(that.lastName.toUpperCase()); } public int hashCode() { // TODO } }
TODO
的地方可使用如下哪些選項,讓 hashCode()
和 equals()
保持一致?
return 42;
return firstName.toUpperCase();
return lastName.toUpperCase().hashCode();
return firstName.hashCode() + lastName.hashCode();
<br />
以前咱們已經對不可變對象的相等性進行了討論,那麼可變類型對象會是怎樣呢?
回憶以前咱們對於相等的定義,即它們不能被使用者觀察出來不一樣。而對於可變對象來講,它們多了一種新的可能:經過在觀察前調用改造者,咱們能夠改變其內部的狀態,從而觀察出不一樣的結果。
因此讓咱們從新定義兩種相等:
對於不可變對象,觀察相等和行爲相等是徹底等價的,由於它們沒有改造者改變對象內部的狀態。
**對於可變對象,Java一般實現的是觀察相等。**例如兩個不一樣的 List
對象包含相同的序列元素,那麼equals()
操做就會返回真。
可是使用觀察相等會帶來隱祕的bug,而且也會讓咱們很容易的破壞聚合類型的表示不變量。假設咱們如今有一個 List
,而後咱們將其存入一個 Set
:
List<String> list = new ArrayList<>(); list.add("a"); Set<List<String>> set = new HashSet<List<String>>(); set.add(list);
咱們能夠檢查這個集合是否包含咱們存入的列表:
set.contains(list) → true
可是若是咱們修改這個存入的列表:
list.add("goodbye");
它彷佛就不在集合中了!
set.contains(list) → false!
事實上,更糟糕的是:當咱們(用迭代器)循環遍歷這個集合時,咱們依然會發現集合存在,可是contains()
仍是說它不存在!
for (List<String> l : set) { set.contains(l) → false! }
若是一個集合的迭代器和contains()
都互相沖突的時候,顯然這個集合已經被破壞了。
發生了什麼?咱們知道 List<String>
是一個可變對象,而在Java對可變對象的實現中,改造操做一般都會影響 equals()
和 hashCode()
的結果。因此列表第一次放入 HashSet
的時候,它是存儲在這時 hashCode()
對應的索引位置。可是後來列表發生了改變,計算 hashCode()
會獲得不同的結果,可是 HashSet
對此並不知道,因此咱們調用contains
時候就會找不到列表。
當 equals()
和 hashCode()
被改動影響的時候,咱們就破壞了哈希表利用對象做爲鍵的不變量。
下面是 java.util.Set
規格說明中的一段話:
注意:當可變對象做爲集合的元素時要特別當心。若是對象內容改變後會影響相等比較並且對象是集合的元素,那麼集合的行爲是不肯定的。
不幸的是,Java庫堅持它對可變類型的 equals()
的實現,即聚合類使用觀察相等,不過也有一些可變類型(例如 StringBuilder
)使用的是行爲相等。
咱們從上面的例子和分析能夠知道**可變類型的equals()應該實現爲行爲相等。**這一般都意味着兩個對象只有在是索引別名的時候equals()
纔會返回真。索引可變類型的 equals()
和 hashCode()
應該直接從 Object
繼承。
對於須要觀察相等操做的可變類型(即當前狀態下是否「看起來」同樣),最好是設計一個新的操做,例如similar()
或 sameValue()
. 它們的實現或許和上文中的私有方法 sameValue()
類似(可是是公有的)。不幸的是Java沒有采起這種設計。
<br />
對於不可變類型:
equals()
應該比較抽象值是否相等。這和 equals()
比較行爲相等性是同樣的。hashCode()
應該將抽象值映射爲整數。因此不可變類型應該同時覆蓋 equals()
和 hashCode()
.
對於可變類型:
equals()
應該比較索引,就像 ==
同樣。一樣的,這也是比較行爲相等性。hashCode()
應該將索引映射爲整數。因此可變類型不該該將 equals()
和 hashCode()
覆蓋,而是直接繼承 Object
中的方法。Java沒有爲大多數聚合類遵照這一規定,這也許會致使上面看到的隱祕bug。
Bag
假設 Bag<E>
是一個可變聚合類型,它表示的是一個multiset(元素能夠出現屢次並且無序)。它的操做以下:
/** make an empty bag */ public Bag<E>() /** modify this bag by adding an occurrence of e, and return this bag */ public Bag<E> add(E e) /** modify this bag by removing an occurrence of e (if any), and return this bag */ public Bag<E> remove(E e) /** return number of times e occurs in this bag */ public int count(E e)
運行下面的代碼:
Bag<String> b1 = new Bag<>().add("a").add("b"); Bag<String> b2 = new Bag<>().add("a").add("b"); Bag<String> b3 = b1.remove("b"); Bag<String> b4 = new Bag<>().add("b").add("a"); // swap!
如下那些選項在運行事後爲真?
[x] b1.count("a") == 1
[ ] b1.count("b") == 1
[x] b2.count("a") == 1
[x] b2.count("b") == 1
[x] b3.count("a") == 1
[ ] b3.count("b") == 1
[x] b4.count("a") == 1
[x] b4.count("b") == 1
Bag behavior
若是 Bag
實現的是行爲相等,如下哪一些表達式爲真?
[ ] b1.equals(b2)
[x] b1.equals(b3)
[ ] b1.equals(b4)
[ ] b2.equals(b3)
[ ] b2.equals(b4)
[x] b3.equals(b1)
Bean bag
若是 Bag
是Java API的一部分,即它可能實現的是觀察相等,如下哪一些表達式爲真?
[ ] b1.equals(b2)
[x] b1.equals(b3)
[ ] b1.equals(b4)
[ ] b2.equals(b3)
[x] b2.equals(b4)
[x] b3.equals(b1)
咱們以前提到過原始/基本類型和它們的對應的包裝(對象)類型,例如int
和Integer
。包裝類型的equals()
比較的是兩個對象的值:
Integer x = new Integer(3); Integer y = new Integer(3); x.equals(y) → true
可是這裏有一個隱祕的問題: ==
被重載了。對於 Integer
這樣的類型, ==
判斷的是索引相等:
x == y // returns false
可是對於基本類型 int
, ==
實現的是行爲相等:
(int)x == (int)y // returns true
因此你不能真正的將 Integer
和int
互換。事實上Java會自動對 int
和Integer
進行轉換(這被稱做自動裝箱和拆箱 autoboxing autounboxing),這也會致使bug,你應該意識到編譯期發生的類型轉換。思考下面的代碼:
Map<String, Integer> a = new HashMap(), b = new HashMap(); a.put("c", 130); // put ints into the map b.put("c", 130); a.get("c") == b.get("c") → ?? // what do we get out of the map?
Boxes
在上面的代碼中:
表達式 130
在編譯期的類型是什麼?
--> int
在 a.put("c", 130)
執行後,Map中表示130的值會是什麼類型?
--> Integer
a.get("c")
在編譯期中的類型是什麼?
--> Integer
Circles
Map<String, Integer> a = new HashMap<>(), b = new HashMap<>(); a.put("c", 130); // put ints into the map b.put("c", 130);
畫出上面代碼執行後的快照圖,在你的快照圖中有幾個 HashMap
對象?
--> 2
在你的快照圖中有幾個 Integer
對象?
--> 2
Equals
Map<String, Integer> a = new HashMap<>(), b = new HashMap<>(); a.put("c", 130); // put ints into the map b.put("c", 130);
在上面代碼執行後, a.get("c").equals(b.get("c"))
會返回什麼?
--> true
a.get("c") == b.get("c")
會返回什麼?
--> false
Unboxes
如今假設你將 get()
的結果存儲在int
變量中:
int i = a.get("c"); int j = b.get("c"); boolean isEqual = (i == j);
在上面代碼執行後, isEqual
的返回值是什麼?
--> true
<br />
HashSet
和 HashMap
)正常工做。相等是實現抽象數據類型中的一部分。如今咱們將本文的知識點與咱們的三個目標聯繫起來:
Object
中的實現,實現不可變類型時必定要覆蓋它們。</font>