Java容器——Set和顺序存储

Wesley13
• 阅读 569

    当Set使用自己创建的类型时,存储的顺序如何维护,在不同的Set实现中会有不同,而且它们对于在特定的Set中放置的元素类型也有不同的要求:

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实现类而必须定义的方法:

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()方法,因此在基类中也有该方法。

    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()定义的顺序,这里是降序)保存元素

    如果我们尝试着将没有恰当地支持必须操作的类型用于这些方法的Set,那么就会有麻烦了。对于没有重新定义hashCode()方法的SetType或TreeType,如果将它们放置到任何散列表中都会产生重复值,这样就违反了Set的基本约定。这相当烦人,因为这种情况甚至不会有运行时错误。

点赞
收藏
评论区
推荐文章
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
java基础(五)集合
!(https://images2015.cnblogs.com/blog/875181/201609/875181201609211007331061187286566.png)Collection接口是集合类的根接口,Java中没有提供这个接口的直接的实现类。但是却让其被继承产生了两个接口,就是Set和List。Set中不能包含重复的元素。L
Stella981 Stella981
3年前
Set针对复杂对象去重问题
Set针对复杂对象去重问题​在项目中我们经常使用set,因其可以去重特性,平时使用较多的是基础数据类型,Set<Integer,Set<Long等,这些在使用中都没碰到什么问题。最近在项目中碰到自定义对象去重,用后创建的对象去覆盖set中type相同的对象,于是想到Set这个集合类型,并且重写了自定义对象的equals()和hashC
Wesley13 Wesley13
3年前
Java集合面试题
CollectionSet和hashCode以及equals方法的联系Set内存放的元素为什么不可以重复,内部是如何保证和实现的?List和Set区别List和Map区别Arraylist与LinkedList区别ArrayList与Vector区别Arraylist与LinkedList默认空间是
Stella981 Stella981
3年前
HashSet和TreeSet
 Set是java中一个不包含重复元素的collection。更正式地说,set不包含满足e1.equals(e2)的元素对e1和e2,并且最多包含一个null元素。正如其名称所暗示的,此接口模仿了数学上的_set_抽象。HashSet与TreeSet都是基于Set接口的实现类。其中TreeSet是Set的子接口Sor
Wesley13 Wesley13
3年前
Java内功系列
面试官:你能简单介绍List和Set有什么区别吗?小憨:List是一个有序的集合,在内存是连续存储的,可以存储重复的元素,List查询快,增删慢;Set是一个无序的集合,在内存中不连续,不可以存储重复的元素,Set增删快,查询慢;面试官:那HashSet是如何保证元素不重复的?小憨:3分钟。。。
Stella981 Stella981
3年前
List、Map、Set三个接口存取元素时,各有什么特点
List接口以特定索引来存取元素,可以有重复元素Set接口不可以存放重复元素(使用equals方法区分是否重复)Map接口保存的是键值对(keyvaluepair)映射,映射关系可以是一对一或者多对一(key唯一)Set和Map容器都有基于哈希存储和排序树的两种实现版本。基于哈希存储的版本的实现理论存取时间复杂度是O(1),而基于排序树版本的
Stella981 Stella981
3年前
Redis学习笔记
这篇是接着上篇来的,所以标号就继续了~~~~四、set介绍:set集合元素是不重复的无序的。set类型与list类型有相似之处,如图:!(http://static.oschina.net/uploads/space/2015/1212/170939_JldH_780876.png)命令:①sa
Wesley13 Wesley13
3年前
Java 集合系列
HashSet介绍HashSet是一个没有重复元素的集合。它是由HashMap实现的,不保证元素的顺序,而且HashSet允许使用null元素。HashSet是非同步的。如果多个线程同时访问一个哈希set,而其中至少一个线程修改了该set,那么它必须保持外部同步。HashSet数据结构java.lang
Wesley13 Wesley13
3年前
Java方面技术点小整理
Java中的集合吗?java中的集合分为value、keyvalueg两种存储值有分为list和setList有序的,可以重复Set是序的,不可以重复的根据equals和hashCode判断如果一个对象要存储在set中,必须重写equals和hashCode的方法;存储keyvalue的为map