Set和存储顺序

在java中使用set容器存储时,除非是使用了诸如Integer和String 的java预定义的类型,这些类型是被设计可以在容器内部使用的。当我们自己创建类型时,我们需要怎么样的形式来维护存储顺序呢?其实在不同的Set实现是具有不同的行为,所以对于在特定的Set实现中,放置的类型也有不同的要求。

举例
类型规定
Set存入Set的每个元素都必须是唯一的,因为Set不保存相同的元素。加入Set的元素必须实现equals()方法来确保对象的唯一性。Set与Collection有完全一样的接口,Set接口不保证维护元素的次序。
HashSet为快速查找而设计的Set。存入HashSet的元素必须定义hashCode()
TreeSet为保证次序的Set,底层是树的结构。使用它可以从Set中提取有序的序列。元素必须实现Comparable接口
LinkedList具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的顺序),必须实现hashCode()方法。

下面的实例演示为了成功使用特定的Set实现类型而必须定义的方法:

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

/**
 * @fileName:TypeOfSets
 * @author:ccl
 * @createTime:2019-05-17
 */
class SetType{
    int i;
    public SetType(int i){
        this.i = i;
    }
    // 重写 equals方法
    public boolean equals(Object o){
        return o instanceof SetType && (i == ((SetType)o).i);
    }
    public String toString(){
        return Integer.toString(i);
    }
}

class HashType extends SetType{
    public HashType(int i) {
        super(i);
    }
    //定义hashCode
    public  int hashCode(){
        return i;
    }
}
//有序Set
class TreeType extends SetType implements Comparable<TreeType>{

    public TreeType(int i) {
        super(i);
    }
    @Override
    public int compareTo(TreeType treeType) {
        //不建议使用i-i2 因为很可能溢出
        return treeType.i<i?-1:(treeType.i==i?0:1);
    }
}

public class TypeOfSets {
    static <T> Set<T> fill(Set<T> set,Class<T> type){
        for(int i=0;i<10;i++){
            try {
                set.add(type.getConstructor(int.class).newInstance(i));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return set;
    }
    static <T> void test(Set<T> set,Class<T> type){
        fill(set,type);
        fill(set,type);
        fill(set,type);
        System.out.println(set);
    }
    public static void main(String[] args) {
		//学习的话 记得打断点
   		 //先走我们自定义的hashCode,如果set中有重复哈希值,那么再走equals方法
        test(new HashSet<HashType>(),HashType.class); 
        //先走我们自定义的hashCode,如果set中有重复哈希值,那么再走equals方法 
        test(new LinkedHashSet<HashType>(),HashType.class);
        // 此处我定义为降序
        test(new TreeSet<TreeType>(),TreeType.class);
		// 大家思考此处为什么会有重复值呢?toString的话
        test(new HashSet<SetType>(),SetType.class);
        test(new HashSet<TreeType>(),TreeType.class);
        test(new LinkedHashSet<SetType>(),SetType.class);
        test(new LinkedHashSet<TreeType>(),TreeType.class);
        try {
        	//报异常 SetType cannot be cast to java.lang.Comparable
            test(new TreeSet<SetType>(),SetType.class);
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
        try {
      	  //报异常 SetType cannot be cast to java.lang.Comparable
            test(new TreeSet<HashType>(),HashType.class);
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
    }
}


运行结果
[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]
[7, 0, 7, 6, 9, 3, 4, 6, 0, 0, 8, 7, 1, 1, 8, 5, 5, 3, 2, 4, 1, 3, 6, 4, 9, 2, 5, 2, 9, 8]
[7, 5, 6, 4, 3, 9, 0, 8, 1, 5, 8, 1, 0, 7, 6, 2, 5, 0, 4, 3, 1, 9, 2, 7, 6, 9, 4, 8, 2, 3]
[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

总结

基类SetType只存储了一个int,并且通过toString()方法产生它的值。因为在Set中存储的类必须具有equals()方法。其等价性基于这个int类型的 i决定。
HashType实现了Comparable接口,没有使用简洁明了的return i-i2,因为他只有在ii2都为无符号int时才正确(假如有unsigned关键字),因为假如i是很大的正整数,而i2是个很大的负整数,那么其差值会溢出并且产生负值
如果我们尝试在TreeSet中使用没有实现的Comparable的类型,那么将抛出异常。

思考答案 因为会调用默认的hashCode方法,这是合法的行为,即使它对于你的结果是不正确的。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐