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的基本约定。这至关烦人,由于这种状况甚至不会有运行时错误。

相关文章
相关标签/搜索