Java☞Set分析

Set

首先给出Set的定义:

public interface Set extends Collection {}

这里只能得到Set继承了Collection接口,是个集合。但是,在官方文档中我们还能看到这么一句话:

A collection that contains no duplicate elements.  More formally, sets
  contain no pair of elements e1 and e2 such that
  e1.equals(e2), and at most one null element.  As implied by
  its name, this interface models the mathematical set abstraction.

翻译一下就是:
首先Set是个不包含重复元素的集合,再正式点说,Set不包含成对的元素,也就是说不会存在e1.equals(e2)这个情况,另外最多有一个null元素。正如它的名字那样,这个接口模拟了数学集抽象。
我觉得吧,简单点说:set集合特点:不重复、最多有一个null值。
其实最重要的要看看Set的实现类,比如HashSet、TreeSet、LinkedHashSet。

一、HashSet

首先给出HashSet的定义:

public class HashSet
    extends AbstractSet
    implements Set, Cloneable, java.io.Serializable{}

由此容易知道,HashSet实现了Set接口,具备Set的一些方法和特性。除此之外,还可以被克隆,可以序列化。
HashSet特点:

  • 集合中元素无序,即插入顺序和遍历顺序可能不一样;
  • 不重复,可插入一个null值,符合Set的特点;
  • 不是线程安全;
  • 底层实现紧跟HashMap,说白了就是HashMap那套存储结构,数组+链表;

HashSet存储对象---注意事项

因为HashSet的一大特点就是不重复,所以在实际工程中考虑用HashSet存储对象的时候一定,一定,一定要记得重写hashCode()和equals()方法
先举个例子:

HashSet hs = new HashSet<>();
Student s1 = new Student("老王","23");
Student s2 = new Student("老王","23");
hs.add(s1);
hs.add(s2);

按照正常人的思维,请问上面的两个对象s1和s2是不是相同的?
答案是肯定相同呀!
可是,我想说的是,如果我们在工程项目中你用了HashSet想实现内部元素不重复,但是却没有重写hashCode()和equals()方法,那么恭喜你入坑了!!!上面的代码运行无误,并且你会发现hs.size()=2;那么是为什么呢?
因为,hs.add()方法在添加元素的时候,首先判断对象s2的哈希值,这个哈希值对应着这个对象的存储地址,如果s2的哈希值在HashSet集合中之前没有出现过,那么直接插入。很显然,上面的代码s1和s2的哈希值不同,插入成功。
那么我们可能就想了,HashSet存储对象的 时候不能对比引用,要对比对象中属性的值才行,的确,如果我们在用HashSet存储的时候,重写equals方法不就行了嘛,在equals方法内分别对比属性的值,当新插入对象的属性值在集合中已经存在的时候就不插入了。这样的话,貌似也没有hashCode()方法什么事儿呀?的确,如果只重写equals方法,也能保证hashSet中存储的对象是不重复的。但是,但是,但是,我们想呀,如果HashSet中原有100个元素了,当你再新添加一个对象的时候,你有没考虑你要对比属性多少次???这个新添加的对象要依次和集合中原有100个对象依次做属性对比,这100次equals方法执行都是false的时候,才允许插入。那如果是随着HashSet容量的增大呢,简直是恐怖如斯呀。。。
这个时候hashCode方法就站出来了 ,在执行equals方法之前先计算两个对象的哈希值,如果哈希值相同了再去对比各个属性值。那么这个时候我又有疑问了,hashCode值不需要在新添对象的时候与已有对象一一对比吗?查阅源码发现,HashSet的底层实现用的HashMap的算法,在执行添加操作的时候,会把key值哈希,根据这个值去找应该存储的内存地址,因此,在这里新添加的对象对比哈希值时,只需要计算新对象的哈希值,然后这个值对应的地址空间查看是否已有值存在即可。因此,想要使用HashSet存储对象的时候,一定要记得重写hashCode()和equals()方法。

s1.equals(s2) == true,那么他们的hashCode相同吗?

按照Java中规定,其hashCode一定相同。(Java中对equals和hashCode方法的规定就是这样的)
但我们知道,其实如果Java不规定的话,答案是不一定的。首先,如果工程师没有重写equals和hashCode方法的话,两个对象值相同(s1.equals(s2) == true),那么他们的hashCode一定相同;但是,如果工程师只重写了equals方法,没有重写hashCode方法,那么hashCode就不相等了嘛。

两个对象的hashCode相同,这两个对象s1和s2相同吗?

这个很显然,不一定呀。

给出Java对equals和hashCode方法的规定:

1.如果两个对象相同,那么它们的hashCode值一定要相同;
2.如果两个对象的hashCode相同,它们并不一定相同(这里说的对象相同指的是用eqauls方法比较)。 如不按要求去做了,会发现相同的对象可以出现在Set集合中,同时,增加新元素的效率会大大下降。
3.equals()相等的两个对象,hashcode()一定相等;equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。 换句话说,equals()方法不相等的两个对象,hashcode()有可能相等(我的理解是由于哈希码在生成的时候产生冲突造成的)。反过来,hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。

二、TreeSet

首先给出TreeSet的定义:

public class TreeSet extends AbstractSet
    implements NavigableSet, Cloneable, java.io.Serializable{}

由此可知,TreeSet具有一些导航功能,可被克隆,可序列化;继承了AbstractSet,而AbstractSet又实现了Set接口;因此TreeSet具备Set的一些性质。
TreeSet特点:

  • 不重复
  • 非线程安全
  • 有序(这个有序不是说插入顺序和遍历顺序一致,而是说TreeSet底层借用TreeMap的实现,因而也是红黑树的数据结构。可以在构造函数中传入一个比较器,默认是自然排序)

LinkedHashSet

首先给出定义:

public class LinkedHashSet
    extends HashSet
    implements Set, Cloneable, java.io.Serializable {}

由此可知,LinkedHashSet是HashSet的子类,实现了Set接口,可被克隆,可序列化。
LinkedHashSet特点:

  • 不重复
  • 有序,即插入顺序和遍历顺序一致
  • 非线程安全
  • 底层实现借助LinkedHashMap

你可能感兴趣的:(Java☞Set分析)