Java基础-Collection子接口之Set接口
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
学习Collection接口时,记得Collection中可以存放重复元素,也可以不存放重复元素,那么我们知道List中是可以存放重复元素的。那么不重复元素给哪里存放呢?那就是Set接口,它里面的集合,所存储的元素就是不重复的。
一.Set接口的特点
一个不包含重复元素的 collection。更确切地讲,set 不包含满足 e1.equals(e2) 的元素对 e1 和 e2,并且最多包含一个 null 元素。正如其名称所暗示的,此接口模仿了数学上的 set 抽象。在所有构造方法以及 add、equals 和 hashCode 方法的协定上,Set 接口还加入了其他规定,这些规定超出了从 Collection 接口所继承的内容。出于方便考虑,它还包括了其他继承方法的声明(这些声明的规范已经专门针对 Set 接口进行了修改,但是没有包含任何其他的规定)。
Set集合有多个子类,这么我们介绍其中的HashSet,LinkedHashSet这两个集合。Set结合取出元素的方式可以采用迭代器和增强for。
二.HashSet集合介绍
此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 null 元素。 总结HashSet有以下结果特点:
1>.无序集合,也就是说存取元素和取出元素的顺序不一定是相同的;
2>.没有索引;
3>.不存储重复元素;
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 package cn.org.yinzhengjie.note; 8 9 import java.util.HashSet; 10 import java.util.Iterator; 11 import java.util.Set; 12 13 public class HashSetDemo { 14 public static void main(String[] args) { 15 Set<String> set = new HashSet<String>(); 16 set.add("yinzhengjie"); 17 set.add("尹正杰"); 18 set.add("2018"); 19 set.add("yinzhengjie"); //第二次添加同一个元素时,并不会放入HashSet集合中。 20 set.add("java"); 21 set.add("Big Data"); 22 23 //用迭代器进行遍历 24 System.out.println("第一种遍历方式"); 25 Iterator<String> it = set.iterator(); 26 while(it.hasNext()) { 27 System.out.println("\t"+it.next()); 28 } 29 30 System.out.println("第二种遍历方式"); 31 for (String string : set) { 32 System.out.println("\t"+string); 33 } 34 } 35 } 36 37 /* 38 以上代码执行结果如下: 39 第一种遍历方式 40 尹正杰 41 2018 42 java 43 Big Data 44 yinzhengjie 45 第二种遍历方式 46 尹正杰 47 2018 48 java 49 Big Data 50 yinzhengjie 51 */
三.哈希表的数据结构
哈希表其实就是链表和数组的结合体。哈希表底层使用的也是数组机制,数组中也存放对象,而这些对象往数组中存放时的位置比较特殊,当需要把这些对象给数组中存放时,那么会根据这些对象的特有数据结合相应的算法,计算出这个对象在数组中的位置,然后把这个对象存放在数组中。而这样的数组就称为哈希数组,即就是哈希表。
当向哈希表中存放元素时,需要根据元素的特有数据结合相应的算法,这个算法其实就是Object类中的hashCode方法。由于任何对象都是Object类的子类,所以任何对象有拥有这个方法。即就是在给哈希表中存放对象时,会调用对象的hashCode方法,算出对象在表中的存放位置,这里需要注意,如果两个对象hashCode方法算出结果一样,这样现象称为哈希冲突,这时会调用对象的equals方法,比较这两个对象是不是同一个对象,如果equals方法返回的是true,那么就不会把第二个对象存放在哈希表中,如果返回的是false,就会把这个值存放在哈希表中。也就是说多个对象可能存放在同一块存储空间中,当第一个对象来到数组的存储空间时,这块空间存储的是第一个进入该区域的对象地址,当第二个对象再次进入这个空间时,这个对象的内存地址并不直接保存在数组中,而是保存在第一个存入的对象中。说道HashSet不得不说一下加载因子,所谓的加载因子就是一个触发数组扩容的指标。虚拟机默认是0.75,初始容量,数组长度默认是16。也就是说当数组的长度超过12时,就会扩容,而之前存在当前数组中的地址又会重新进行哈希。简图如下:
2>.字符串对象的哈希值
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 package cn.org.yinzhengjie.note; 8 9 class Teacher { 10 /* 11 * 没有做重写父类,每次运行结果都是不同整数 12 * 如果子类重写父类的方法,哈希值,自定义的 13 * 存储到HashSet集合的依据 14 * 15 */ 16 @Override 17 public int hashCode() { 18 return 100; 19 } 20 } 21 22 public class HashDemo { 23 public static void main(String[] args) { 24 Teacher t = new Teacher(); 25 //调用自定义的类的hashCode方法 26 int hashId = t.hashCode(); 27 System.out.println(hashId); 28 29 String s1 = new String("abc"); 30 String s2 = "abc"; 31 System.out.println(s1.hashCode()); 32 System.out.println(s2.hashCode()); 33 } 34 } 35 36 37 /* 38 以上代码执行结果如下: 39 100 40 96354 41 96354 42 */
我们可以明显看出自己定义的类调用hashCode()默认返回值为100,最终打印结果和我们预期的一样,而我们在创建一个字符串的时候,发现两个值相等的字符串他们的地址竟然也是一样的!这是怎么回事呢?这个时候我们就不得不去看一下String重写的hashCode源码啦。
3>.哈希表的存储过程
四.哈希表的存储自定义对象
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 package cn.org.yinzhengjie.note2; 8 9 public class Person { 10 private String name; 11 private int age; 12 public String getName() { 13 return name; 14 } 15 public void setName(String name) { 16 this.name = name; 17 } 18 public int getAge() { 19 return age; 20 } 21 public void setAge(int age) { 22 this.age = age; 23 } 24 public Person(String name, int age) { 25 super(); 26 this.name = name; 27 this.age = age; 28 } 29 public Person() { 30 super(); 31 } 32 @Override 33 public String toString() { 34 return this.name + "---" + this.age; 35 } 36 @Override 37 public int hashCode() { 38 final int prime = 31; 39 int result = 1; 40 result = prime * result + age; 41 result = prime * result + ((name == null) ? 0 : name.hashCode()); 42 return result; 43 } 44 @Override 45 public boolean equals(Object obj) { 46 if (this == obj) 47 return true; 48 if (obj == null) 49 return false; 50 if (getClass() != obj.getClass()) 51 return false; 52 Person other = (Person) obj; 53 if (age != other.age) 54 return false; 55 if (name == null) { 56 if (other.name != null) 57 return false; 58 } else if (!name.equals(other.name)) 59 return false; 60 return true; 61 } 62 }