Java容器——Set和順序存儲

    當Set使用本身建立的類型時,存儲的順序如何維護,在不一樣的Set實現中會有不一樣,並且它們對於在特定的Set中放置的元素類型也有不一樣的要求:java

Set(interface) 存入Set的每一個元素都必須是惟一的,由於Set不保存重複元素。加入Set的元素必須定義equals()方法以確保對象的惟一性。Set和Collection具備徹底同樣的接口,但Set不保證元素的順序。
HashSet* 爲快速查找而設計的Set。存入HashSet的元素必須定義hashCode()方法
TreeSet 一種可維護元素順序的Set,底層爲樹結構。使用它能夠從Set中提取有序的序列。元素必須實現Comparable接口。
LinkedHashSet 具備HashSet的查詢速度,並且內部使用鏈表維護元素的順序(插入的順序),因而在使用迭代器遍歷Set時,結果會按元素插入的順序顯示。元素也必須定義hashCode()方法

    在HashSet打*號,表示若是沒有其餘的限制,這就應該是默認的選擇,由於它的速度很快。
編程

    你必須爲散列存儲和樹形存儲都定義一個equals()方法,可是hashCode()只有在這個類將會被放入HashSet或者LinkedHashSet中才是必須的。可是對於良好的變成風格而言,你應該在覆蓋equals()方法的同時覆蓋hashCode()方法。下面的示例演示了爲了成功的使用特定的Set實現類而必須定義的方法:
spa

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;

class SetType {
    int i;
    public SetType (int n) { i = n; }
    public boolean equals(Object obj) {
        return obj instanceof SetType && (i == ((SetType)obj).i);
    }
    public String toString() { return Integer.toString(i); }
}

//能正常被HashSet,LinkedHashSet使用的類型
class HashSetType extends SetType {
    public HashSetType(int n) { super(n); }
    public int hashCode() { return i; }
}

//能正常被TreeSet使用的類型
class TreeSetType extends SetType implements Comparable<TreeSetType>{
    public TreeSetType(int n) { super(n); }
    public int compareTo(TreeSetType o) {
        return (o.i < i ? -1 : (o.i > i ? 1 : 0));//降序排列
    }
}

public class TypesForSets {
    static <T> Set<T> fill(Set<T> set, Class<T> clazz) {
        try {
            for (int i = 0; i < 10; i++) {
                set.add(clazz.getConstructor(int.class).newInstance(i));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return set;
    }
    static <T> void test(Set<T> set, Class<T> clazz) {
        fill(set, clazz);
        fill(set, clazz);//嘗試重複向Set中添加
        fill(set, clazz);
        System.out.println(set);
    }
    public static void main(String[] args) {
        //正確的裝法
        System.out.println("---------Correct----------");
        test(new HashSet<HashSetType>(), HashSetType.class);
        test(new LinkedHashSet<HashSetType>(), HashSetType.class);
        test(new TreeSet<TreeSetType>(), TreeSetType.class);
        //錯誤的裝法
        System.out.println("---------Wrong----------");
        test(new HashSet<SetType>(), SetType.class);
        test(new HashSet<TreeSetType>(), TreeSetType.class);
        test(new LinkedHashSet<SetType>(), SetType.class);
        test(new LinkedHashSet<TreeSetType>(), TreeSetType.class);
        try {
            test(new TreeSet<SetType>(), SetType.class);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        try {
            test(new TreeSet<SetType>(), SetType.class);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

    執行結果(樣例):
設計

---------Correct----------
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
---------Wrong----------
[1, 1, 5, 0, 7, 3, 4, 6, 5, 9, 8, 0, 7, 5, 9, 6, 
    8, 2, 4, 1, 7, 4, 3, 6, 8, 2, 2, 0, 9, 3]
[3, 5, 1, 8, 5, 4, 1, 0, 9, 3, 0, 8, 5, 7, 6, 9, 
    7, 3, 4, 0, 7, 6, 2, 1, 2, 8, 6, 9, 4, 2]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 
    6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 
    6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
java.lang.ClassCastException: 
    SetType cannot be cast to java.lang.Comparable
java.lang.ClassCastException: 
    SetType cannot be cast to java.lang.Comparable

    爲了證實哪些方法是對於某種特殊的Set是必須的,同時也爲了不代碼重複,咱們建立了三個類型。基類SetType只存儲了一個int,而且經過toString()方法產生它的值。由於全部在Set中存儲的類型都必須具備equals()方法,所以在基類中也有該方法。
code

    HashSetType繼承自SetType,並添加了hashCode()方法,該方法對於放入Set的散列實現中的對象來講是必需的。
對象

    TreeType實現了Comparable接口,若是一個對象被用於任何種類的排序容器中,例如TreeSet,那麼它必須實現這個接口。注意:在compareTo()方法中,我沒有使用簡潔明瞭的形式return o.i-i,由於這是一個常見的編程錯誤,它只有在i和i2都是無符號的int(若是Java確實有unsigned關鍵字的話)才能正常工做。對於有符號的int,它就會出錯,由於int不夠大,不足以表現兩個有符號的int的差。例如o.i是很大的正數並且i是很小的負數時,i-j就會溢出並返回負值,這就不對了。
排序

    你一般但願compareTo()產生與equals()一致的天然順序。若是equals()對於某個特定的比較返回true,那麼compareTo()對於該比較就應該返回0,反之亦然。
繼承

    在TypesForSets中,fill()和test()方法都是用泛型定義的,這是爲了不代碼重複。爲了驗證某個Set的行爲,test()會在被測Set上調用三次,嘗試着在其中添加劇復對象。fill()方法接受任意類型的Set,以及對應類型的Class對象,它使用Class對象來發現構造器並構造對象後添加到Set中。
接口

    從輸出能夠看到,HashSet以某種順序保存全部的元素(這結果是我用 jdk1.7.0_79 跑出來的,而書中描述是用的jdk1.5,所以不知道是否是這裏存在的差別。我這裏使用HashSet的元素的結果是有序的,但書中順序是亂的),LinkedHashSet按照元素插入的順序保存元素,而TreeSet則按照排序(按照compareTo()定義的順序,這裏是降序)保存元素
get

    若是咱們嘗試着將沒有恰當地支持必須操做的類型用於這些方法的Set,那麼就會有麻煩了。對於沒有從新定義hashCode()方法的SetType或TreeType,若是將它們放置到任何散列表中都會產生重複值,這樣就違反了Set的基本約定。這至關煩人,由於這種狀況甚至不會有運行時錯誤。

相關文章
相關標籤/搜索