Java_集合—Set

集合篇:

Java_Collection_集合

Java_集合—Collection和Iterator

Java_集合—List

Java_集合—Set

Java_集合—Map

Java_集合的工具類—Collections



上一節我們講了List,這一節來看看Set集合。

說到Set,最大的特點是無序,且元素是不重複的。但TreeSet還是有序的,爲什麼這個說呢?往下看吧!

先看代碼,先來證明一下是有序的還是無序的。


從代碼上和輸出的結果可以明顯的看出,輸出的和添加的順序是不一樣的。

再來看看TreeSet是有序的。看代碼


咦,仔細一看,也是無序的啊。對,沒錯,還是無序的,但你再仔細一看,發現還是有序的。撇開添加時候的順序,只看輸出的結果,是不是有序的。

嗯嗯,這就對了,這就是TreeSet和HashSet的最大的區別,兩者都是無序的,但TreeSet會按照特定的順序對裏面的元素進行排序,而Hash卻不會。這就是前面說的,爲什麼Set是無序的,而TreeSet是有序的,這裏所說的有序和無序並不能混爲一談,前者是指插入的順序和查詢出來的順序是不一致的,後者指的是,所插入的元素會按照元素自身的排序方式(自然排序)進行排序。例如String,會按照數字先排序,然後按照字母的順序排序。所以,要分清楚,有序和無序。

說完無序,Set集合的另一個特點就是元素不能重複。不能重複的是怎麼來的呢?先看一看代碼:


看這段代碼。line15和line24都是一個set集合,所不同的是,一個存儲的是String,一個存儲的是Student,Student在line32-33,一個空類,裏面什麼都沒有,line16-18,以及line25-26是往set集合中插入數據,然後就是遍歷集合,都很簡單,一看就明白,接下來看輸出的內容,第一個集合,插入三個元素,最終只打印出2個,有一個沒插進去,這很好解釋,重複了嘛,我們在插入的時候插入了兩個String-->“123”,然後再看第二個集合打印出來的數據(其實這是對象在內存中的地址),先不要管打印出來的是什麼,看個數,兩個嘛,對吧,再回去看看第二個集合是怎麼插進去的,很簡單,都是new了一個Student就直接插進去了,沒做任何的操作,爲什麼呢?這兩個Student不是一樣的麼? 重點這就來了:

看代碼:


代碼只做了小小的改動,line25-30做了一點改動,目的只有一個,那就是打印出他們的HashCode是不是相同,我們看打印出來的東西,分割線下第一行,那兩個int值是否相同,很明顯就不一樣嘛,對吧。沒錯就是不一樣,這也可以看得出,其實Set集合插入的時候要對比一下HashCode是否相同,如果相同就不插入,不相同就視爲不是同一個對象,就插入。信不信,不信你可以把前面String的三個HashCode打印出來看看第三個HashCode是不是和第一個的HashCode是不是相同的,難道它就只對比HashCode麼?沒那麼簡單。往下看:


我們現在重寫了一下Student的hashCode() 方法,全部返回1,那麼所有的HashCode就相同咯,再看打印出來的,打印出來的第一個行可以看到,兩個HashCode是相同的,但我們遍歷集合的時候還是可以打印出兩條數據,那麼可以得出的是,集合裏面確實有兩個元素,唉,你這騙子,前面還跟我說HashCode,我還真信了,到這裏又不靠譜了,別急,往下看,

還是原來的代碼,還是原來的配方,還是原來的味道,哈哈。跟上面的不同的是:我在Student裏面重寫了equal()方法,方法裏面也很簡單,就一句,拿兩個對象的HashCode

進行對比,相同就返回true,不同就返回false咯。然後看打印輸出的是什麼,我們這裏可以看到,遍歷集合的時候,只遍歷出來一個元素,那就很簡單的說明集合裏面就只有一個元素。(臥槽,你這是截圖截出來的,萬一你截圖你故意少截一行咋辦,不信你就去試試唄。嘿嘿。)

好了,例子都到這裏了,下面來梳理一下set集合在添加的時候具體的執行的流程,它是怎麼分辨出兩個元素是否是同一個元素?

首先,在插入的時候,會去對比兩個元素的HashCode,如果兩個的HashCode不同,那麼就不管3721直接插入,如果兩個元素的HashCode是相同的話,那麼就執行它的equal()方法,如果equal返回的是true,那麼就會認定是同一個元素,就不再插入,就這麼簡單,首先對比HashCode,然後再對比equal(),這裏要說明的是,插入的時候是跟目前集合裏面所有的元素都對比一遍。還有一點,set集合可以插入空對象(null),但同樣的道理,有且只能插入一個。


唉,扯到這裏,好累,全都是手打,稍微緩一緩,繼續扯。


扯完了Set,接下來看看HashSet 唄,其實HashSet 真沒什麼好扯的,都跟上面的差不多,有一點要知道的是:HashSet底層的數據結構是哈希表。其餘沒什麼了,我也就不扯了,重複一遍,沒意義。

乾脆來扯扯HashCode和equal()的設計的巧妙性吧!上面的例子,Student的HashCode我是直接返回的是1,實際的場景中。我們可以將學生的年齡或者學號作爲HashCode,或者在equal中直接對比學生的學號即可,只要學生的學號一致就認爲是同一個學生,以保證其唯一性。爲什麼要設置巧妙一點呢?很簡單嘛,假如你HashCode直接返回的是學生的年齡,那麼是不是很多學生的年齡是一致的,所以是不是還要執行equal,要執行多一個方法要不要耗性能?對吧,所以如果可以直接通過HashCode來對對象進行對比的話,是不是省事很多,當然爲什麼要重寫呢?我不重寫不就像第一個一樣麼?直接new一個過去的時候會自動添加,不會添加不進去。這樣的話,你試想一下,如果都是這樣做的話,那麼我new 學生對象的時候,我學號,年齡性別等都傳同樣的進去,也就是同一個學生,這樣是不是一個學生可以無限的插入,你覺得有意義麼?對吧,所以。HashCode要做到恰到好處。

接下來看看TreeSet,TreeSet也沒什麼好講的,既然TreeSet裏面的元素是有序的。那麼就來說說元素的有序是怎麼來的?

先把滾動條往上拉,拉到第二個例子,這裏可以看到我們的TreeSet是有序的。既然是有序的,那麼具體是按照什麼排的呢?我們又能不能自己修改其排序呢?

其實呢,排序也很簡單,我們只需要在對象裏面實現Compareable接口即可排序,裏面你想怎麼寫就怎麼寫,想怎麼排序就怎麼排序。

接下來看代碼:


代碼還是一樣,只不過不同的是:Student實現了Compareable接口。可以對學生進行排序,排序的方式是按照年齡的升序排序。那降序怎麼辦呢?如果我有幾個可以排序的方式,我可以按照身高排序啊。

看代碼:



這段代碼是按照年齡的降序排序的,可以看到我CompareTo方法中排序的時候取的是身高,然後仔細看看,前一段的代碼是this在前,O在後,這一段是O在前,this在後。所以,我們要改變對象的排序方式,很簡單。那我多種排序方式一起排怎麼辦?這個時候你就要深入的瞭解compareTo方法了。在A.compareTo(B)中。AB都是整形的數據。如果A大於B,那麼這個方法返回來的數是1,如果小於,則返回-1.如果等於,則返回0。既然方法的返回值理清楚了這就很簡單了嘛。如果是先按照年齡排序,然後按照身高排序。那麼你可以先對年齡進行排序,如果年齡的CompareTo方法返回的不是0,那麼直接返回,如果是0,繼續比較身高。


compareTo方法就這麼寫,多簡單。如果不信,儘管可以去試試,哈哈!


都瞭解了,但你發現沒有,這有一個很大的毛病,如果我現在是按照年齡排序。將來我要按照身高排序,怎麼辦?

難道我去改代碼嗎?要去改就麻煩了,所以,實際使用中,我們是比較少去實現Compareable接口的,你可以看看TreeSet是不是還有一個構造方法,可以傳遞一個Compare 進去。對,沒錯,我們只要自定義一個Compare 在TreeSet實例化的時候傳遞進去即可,就可以不動原來的代碼。

咦,新的問題 又出現了,那如果類實現了Compareable,然後又傳進去了一個comparator,那怎麼辦呢?以哪個爲準呢?或者兩者都有效,只是先按照這個排,然後按照那個排。這裏可以明確的跟你說,如果兩個都有,以我們傳遞進去的爲準!



好了寫到這裏。好累,睡覺去。。。



發佈了38 篇原創文章 · 獲贊 25 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章