天天看點

JAVA學習筆記——Set集合

1、Collection集合主要包括:

    List:有序(存儲順序和取出順序一樣),可重複

    Set:無序(存儲順序和取出順序不一樣),唯一

2、Set集合是一個接口,主要使用其子類hashSet和TreeSet

3、hashSet

  1)hashSet如何保證值的唯一?

  通過檢視add()方法的源碼,可以發現該方法底層依賴兩個方法:hashCode()和equals()

  步驟:

    首先通過hashCode()方法得到對象的哈希值并比較

     如果哈希值相同,則比較位址值或者調用equals()方法比較

     如果不同,則直接添加到集合中

  按照方法的步驟來說:

    先調用hashCode(),比較其值

      相同,調用equals()方法

        傳回true:說明元素重複,就不添加

        傳回false:說明元素不重複,添加到集合

      不同則直接添加到集合

  注意:如果類沒有重寫這兩個方法,預設使用Object(),一般來說不相同。

  而String重寫了hashCode()方法和equals()方法,是以可以把相同的字元串過濾掉,隻留下不同的。

  

  2)自定義類如何保證用hashSet()存儲的唯一性?

  通常我們建立對象的時候,當對象的成員變量相同的時候我們就預設這兩個對象是同一個,但是在記憶體中并不是,是以在使用hashSet()存儲的時候,我們就需要重寫hashCode()方法和equals()方法。

  方法一:直接讓hashCode()的傳回值為0,并且重寫equals()方法。

  這樣可以讓所有對象的hash值都相同,這樣程式會直接繼續調用equals()方法,這樣就能實作比較對象的成員變量來判斷。

  (注意:哈希表是一種元素為連結清單的數組。綜合了數組和連結清單的優點。)

  但是這種方法會導緻把所有的對象成員都存儲在哈希表的第一個連結清單元素中,雖然解決了重複問題但是效率不高,這種方法并不好。

  方法二:

    如何優化第一種方法呢?

    我們可以讓每個對象的哈希值盡可能不同。由于對象的哈希值是與它們的成員變量相關的,是以我們可以讓對象的哈希值等于多個成員變量的哈希值的和。其中:

    如果是基本引用類型,就直接加值。

    如果是引用類型,就加哈希值。

    通過這種方式來重寫hashCode()方法

    例如:

public int hashCode(){
    return this.name.hashCode()+this.age ;
}
           

    但是即使這樣,傳回值依然有可能是相同的,例如:20+30和30+20最終還是相等的。

  方法三:最終方案,為了盡可能的區分,我們可以讓基本類型的成員變量直接乘以一個數,以此來盡可能的産生不同的結果。例如:

public int hashCode(){
    return this.name.hashCode() + this.age * ;
}
           

    如上,我們也可以直接用eclipse直接生成。

  由上我們可知,hashSet集合的底層是哈希表結構,而哈希表結構主要依賴hashCode()和equals()方法,如果想讓自定義類的對象是根據成員變量相同即為同一個對象的話,就應該重寫這兩個方法。

  3)LinkedHashSet:底層資料結構由哈希表和連結清單組成

           哈希表保證元素唯一性

           連結清單保證有序(存儲和取出)

4.TreeSet:能夠對集合元素按照某種規則排序

  TreeSet集合兩個特點:排序和唯一

   唯一性:根據比較的傳回結果是否為0

   排序分為兩種:

     A:自然排序(元素具備比較性):讓要排序的元素的類自身實作Comparable接口

     B:比較器排序(集合具備比較性):讓集合的構造方法接收一個比較器接口的子類對象Comparator

     

  TreeSet真正的比較是基于元素的compareTo()方法,而這個方法是定義在Comparable裡面的,是以要重寫compareTo()方法,就必須先繼承Comparable接口。這個接口表示的就是自然順序。

  TreeSet集合使用哪種排序主要取決于Set集合使用的構造方法。

    無參構造使用的是自然排序

    

  2)TreeSet如何保證元素唯一性和有序?

  如下:

  

JAVA學習筆記——Set集合

   通過這種方式,TreeSet集合既保證元素的唯一性,又能保證其有序。以上是自然排序,需要繼承Comparable接口并且實作compareTo()方法,在實作compareTo()方法的時候需要注意通過需求分析出排序的主要條件和次要條件,例如:根據學生年齡排序,那麼compareTo()方法重寫方式如下:

   

public int compareTo(Student s){
        //主要條件:年齡
        //傳回結果正數就排在後面,負數就排在前面,0就說明兩個相等
        int result = this.age - s.age ; 
        //如果出現年齡相同的不同學生,這時候就需要思考什麼作為次要條件

        //次要條件:姓名
        //如果年齡相等,就比較姓名,如果不相等,就直接按照年齡的比較結果來排序。
        result = result ==  ? this.name.compareTo(s.name) : result ;
        //name作為String類型自身實作了compareTo()方法,可以直接調用。
        return result ;
    }
           

   接下來,如何通過排序器實作排序?

   我們知道,當使用Set的無參構造的時候使用的是自然排序,那麼當使用形參為比較器接口的構造函數時,使用的就是比較類排序。

public TreeSet(Comparable<? super E> comparator) ;
           

使用該構造方法時,需要傳一個Comparable的執行個體,但是Comparable是一個接口,也就是說,這裡傳的應該是一個Comparable接口的實作類的對象,這樣我們就需要定義一個Comparable接口的實作類,并且實作compareTo()方法。為了排序而建立類太過于浪費,我們可以通過匿名内部類來實作。

TreeSet<Student> ts= new  TreeSet<Student>(new Comparator<Student>() {

            @Override
            public int compare(Student s1, Student s2) {
                 //主要條件:年齡
                int result = s1.getAge() - s2.getAge() ; 

                //次要條件:姓名
                result = result ==  ? s1.getName().compareTo(s2.getName()) : result ;

                return result ;
            };
        }) ;
           

以上是比較器排序方式,但是我們不常用,更常用自然排序。