java.util.Set接口与java.util.List接口一样,都是继承自Collection接口,Set接口拥有Collection接口中的所有方法。Set集合与List集合相比,有以下区别:
(1)List集合是按序存储的,Set集合是无序存储的。
(2)List集合存储的元素可以相同,Set集合存储的元素不会出现重复(重复的元素会被舍弃)。
说明:
HashSet集合通过对象的equals()方法来检查其唯一性,避免添加重复的元素,且该equals()方法依赖于hashcode()方法。
本小节将介绍Set集合的两个实现类java.util.HashSet和java.util.TreeSet。
1.HashSet
java.util.HashSet是Set接口的一个实现类,其底层实现是由java.util.HashMap进行支持的。HashSet是根据对象的哈希值来确定元素在集合中的存储位置,因此元素的存放顺序是无序的,并且由于哈希算法的特点,使得HashSet具有良好的存取和查找性能。
说明:
由于相同的元素具有一样的哈希值,因此可以避免存储重复的元素。
创建一个HashSet集合的方法有:
1)public HashSet ()
无参构造方法,用于构造一个空的HashSet,且该集合的哈希表默认初始容量为16,负载因子为0.75。
说明:
负载因子表示一个哈希表的空间的使用程度,哈希表的初始容量、负载因子和哈希表可用空间之间存在关系:initailCapacity * loadFactor=哈希表可用空间。因此,负载因子越大则哈希表的装填程度越高,能容纳的元素更多,但元素越多,索引的效率就越低;反之,负载因子越小,则哈希表中的元素就越稀疏,索引效率就越高,但会造成空间的浪费。
2)public HashSet (int initialCapacity)
该方法可以用于构造一个初始容量为initialCapacity、负载因子为0.75的空HashSet,其中initialCapacity表示哈希表的初始容量。如果指定的容量为负数,会抛出IllegalArgument Exception异常。
3)public HashSet (int initialCapacity, float loadFactor)
该方法可以用于构造一个初始容量为initialCapacity、负载因子为loadFactor的空HashSet,其中initialCapacity表示哈希表的初始容量,loadFactor表示哈希表的负载因子。如果指定的容量为负数或者负载因子为非正数(0或负数),将会抛出IllegalArgumentException异常。
4)public HashSet (Collection<? extends E> c)
该方法用于构造一个包含指定集合c中所有元素的HashSet,且该HashSet具有默认负载因子0.75。如果给定的集合c为空,将抛出NullPointerException异常。
【例9.4】编写一个存储String类型的HashSet集合,并对集合进行一些简单操作。
上述代码中创建了一个存储String类型的HashSet,然后向集合中添加了“张三”“李四”“王五”“赵六”“张三”5个元素,运行程序,输出结果如图9.7所示。
图9.7 程序运行结果
从图9.7的运行结果可以看出,只有前4个元素存入到了集合中,第2个“张三”没有被存入集合。
如果现在要求使用HashSet来存储姓名、电话号码以及备注信息,由于可能存在同名字的人,而电话号码相对来说比较有唯一性,因此约定只要当姓名和电话号码相同就认为是重复元素(无论备注信息是否相同),这又如何来实现?
【例9.5】编写一个存储Person类型的HashSet集合,用来存储姓名、电话号码以及备注信息。
首先编写Person类来保存姓名、电话号码以及备注信息,且覆写这个类的hashCode()与equals()方法。
提示:
HashSet集合根据存储对象的hashCode()和equals()方法来保证元素的唯一,因此如果向集合中存放自定义的对象,那么为了保证其唯一性,往往需要根据实际需求复写hashCode()和equals()方法。
代码如下:
在Person类中对hashCode()和equals()方法进行了重写。根据要求,姓名和电话号码相同的Person对象视为同一个对象,因此重写equals()方法时,判断如果姓名和电话号码都相同则返回true。另外,为了保证对象的哈希值唯一,且哈希值与对象的姓名和电话号码关联,因此在重写HashCode()方法时,采用Objects.hash(name, tel)作为返回值。
下面编写HashSetPerson.java,使用HashSet来保存Person信息,代码如下:
HashSetPerson.java中创建了一个HashSet<Person>集合temp,并向集合中添加了5个元素,程序运行结果如图9.8所示。
图9.8 程序运行结果
在向HashSet集合中添加元素时,首先JVM会调用当前存入对象的hashCode()方法来获得当前存入对象的哈希值,然后根据这个哈希值计算出元素的存储位置。如果该位置上没有元素则直接将元素存入,如果该位置上有元素存在,则再调用equals()方法判断当前存入的元素和该位置上的元素是否相同(开发人员可以自定义元素相同的条件),如果判断结果为false,就将该元素存入集合,如果判断结果为true,则说明有重复元素,则不存入这个元素。
说明:
在JDK1.8中,哈希表是由数组、链表以及红黑树三种方式来共同实现存储的,因此如果要存入的元素哈希值与其他元素相同(该位置已经有其他元素),但equals()方法返回false,则会以链表的形式将该元素存入集合中。当数组中某个位置存储的元素太多(链表长度大于8),则该位置上的链表转为红黑树进行存储。
从图9.8的运行结果可以看出,new Person("李四","0812-3333002","我不是李四!")没有加入到集合中,因为这个Person对象的name和tel属性与new Person("李四","0812-3333002","我是李四,不过我用张三的电话!")重复。另外,观察集合的输出顺序,可以看出在HashSet中元素的存取位置是无序的,与存入顺序无关。
2.TreeSet
java.util.TreeSet也是Set接口的一个实现类,其底层是由TreeMap进行实现的。与HashSet不同的是,TreeSet是根据对象的某种比较机制来确定元素在集合中的存储位置,这个位置并不是随机的,而是按照比较结果进行有序存放的。因此TreeSet适用于需要进行大量快速检索排序信息的场景。
说明:
由于TreeSet是根据对象提供的某种比较机制来判断元素是否重复以及存放顺序,因此TreeSet要求存入的对象必须是可比较的。(www.daowen.com)
如果比较结果为负数,表示将当前元素放在红黑树的左边,即逆序输出;如果比较结果为正数,则表示将当前元素放在红黑树的右边,即顺序输出;如果比较结果为0,则表示元素重复,放弃存入该元素。
如:"A".compareTo("B")的比较结果是-1,则表示"A"应该放在"B"的左边,在输出的时候会先输出"A",再输出"B"。
创建一个TreeSet集合的方法有:
1)public TreeSet ()
无参构造方法,用于构造一个空的TreeSet,该集合默认根据其元素的自然排序进行排序,且要求存入的元素必须是相互之间可以比较的。
说明:
存入TreeSet中的元素必须实现Comparable接口,且元素与元素之间必须是可以比较的。如果向集合中添加了不合法的数据类型,将抛出ClassCastException异常。
2)public TreeSet(Comparator<? super E> comparator)
该方法可以根据指定比较依据comparator来构造一个的TreeSet集合。
3)public TreeSet(SortedSet<E> s)
该方法可以根据指定排序集s来构造一个的TreeSet集合。
4)public TreeSet(Collection<? extends E> c)
该方法用于构造一个包含指定集合c中所有元素的TreeSet,且该TreeSet会将集合c中的元素按照自然排序进行排序。
【例9.6】编写一个存储String类型的TreeSet集合,集合采用自然排序,向集合添加元素并输出。
上述代码中创建了一个存储String类型的HashSet,为了方便观察String的自然排序结果,向集合中添加“C张三”“A李四”“B王五”“D赵六”“C张三”5个元素,运行程序,输出结果如图9.9所示。
图9.9 程序运行结果
从图中的运行结果可以看出,相同的两个字符串“C张三”没有被存入集合中,且TreeSet中的元素在输出的时候是有序的,最先输出“A李四”,最后输出“D赵六”。
说明:
String类型的数据的比较逻辑为,对于字符串中的每个字符从左向右依次进行比较:
(1)若字符相同,则继续比较下一个字符。
(2)若字符串前面部分的每个字符都一样,而当前字符不一样,则返回两个字符的ASCII码的差值。
(3)若字符串前面部分的每个字符都一样,且其中一个字符串没有更多字符,则返回两个字符串剩余长度的差。
(4)若字符串的每个字符完全一样,返回 0。
如果要向TreeSet中存入自定义对象,则该对象需要实现Comparable接口,或者在创建TreeSet集合时实现Comparator接口。
【例9.7】编写Person类(包含String类型的name以及Sting类型的id),然后Person类实现Comparable接口。覆写compareTo()方法,以Person的id作为比较依据。
代码如下:
Person类实现了Comparable接口,并对 compareTo ()方法进行了重写。如果要存入的Person对象为空,或者是重复对象,则返回0(表示忽略该元素),否则比较元素中的id,返回元素之间id的比较结果。
编写TreeSetPerson.java,创建一个TreeSet来保存Person信息,向集合中添加一些元素并输出,代码如下:
HashSetPerson.java中创建了一个TreeSet<Person>集合temp,并向集合中添加了5个元素,程序运行结果如图9.10所示。
图9.10 程序运行结果
从图9.10的运行结果可以看出,new Person("李四","20200101")因为和new Person("张三","20200101")的id相同,因此没有被存入集合中。然后所有的元素根据id进行排序,从输出结果可以看出,最先输出的是new Person("张三","20200101")。
【例9.8】 编写Student类(包含String类型的name以及Sting类型的id),然后在创建TreeSet集合时实现Comparator接口,并以Student的id作为比较依据。
代码如下:
在创建TreeSet集合的时候采用TreeSet<Student> set = new TreeSet<>(new Comparator<Student>()),并覆写Comparator接口的compare ()方法。如果要存入的Student对象为空,则返回0(表示忽略该元素),否则比较两个元素的id,返回比较结果。
程序运行结果如图9.11所示。
图9.11 程序运行结果
从图9.11的运行结果可以看出,两种实现方法的效果一样,都能够按照id进行排序,并且在存入元素时会根据id去掉重复元素。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。