一、set接口
概念:set接口继承自Collection接口,与List接口不同的是,set接口所储存的元素是不重复的。
二、HashSet集合
概念:是set接口的实现类,由哈希表支持(实际上是一个HashMap集合)。HashSet集合元素的提取顺序与存储顺序不相同。
采用哈希表数据结构存储数据,保证元素唯一性的方式依赖于:hashCode()与equals()方法。
2.1哈希表
什么是哈希表? 链表与数组的组合。
哈希表底层使用的也是数组机制,数组中也存放对象,而这些对象往数组中存放时的位置比较特殊,当需要把这些对象给数组中存放时,
那么会根据这些对象的特有数据结合相应的算法,计算出这个对象在数组中的位置,然后把这个对象存放在数组中。而这样的数组就称为哈希数组,即就是哈希表。
当向哈希表中存放元素时,需要根据元素的特有数据结合相应的算法,这个算法其实就是Object类中的hashCode方法。由于任何对象都是Object类的子类,所以任何对象有拥有这个方法。即就是在给哈希表中存放对象时,会调用对象的hashCode方法,算出对象在表中的存放位置,这里需要注意,如果两个对象hashCode方法算出结果一样,这样现象称为哈希冲突,这时会调用对象的equals方法,比较这两个对象是不是同一个对象,如果equals方法返回的是true,那么就不会把第二个对象存放在哈希表中,如果返回的是false,就会把这个值存放在哈希表中。
总结:保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。
Hashcode方法用来计算哈希值。
hashCode方法计算图:
哈希表数组和链表的结合图:
2.2HashSet存储JavaAPI中的类型元素
给HashSet中存储JavaAPI中提供的类型元素时,不需要重写元素的hashCode和equals方法,因为这两个方法,在JavaAPI的每个类中已经重写完毕,如String类、Integer类等。
练习实例:
1.向哈希表添加元素并且打印
import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; public class Demo01 { //哈希表 public static void main(String[] args) { // TODO Auto-generated method stub method1();
method2();
method3();
method4();
} public static void method1(){ HashSet<String> set=new HashSet<String> (); set.add("abc"); set.add("abc"); set.add("ghi"); System.out.println(set); // }
打印结果:因为唯一性,所以只存储了一个“abc”.
2.打印哈希值
//hashcode 方法(object类中提供) public static void method2(){ String s1=new String("abc"); String s2=new String("abc"); //运行出来的 叫哈希值 运行的hashcode是string类的 System.out.println(s1.hashCode()); System.out.println(s2.hashCode()); }
打印结果:哈希值 hashCode()
3.向哈希表中添加自定义类的元素并打印
自定义Person类:
public class Person { private String name; private int age; public Person(){ } //重载构造方法,创建时赋值 public Person(String name, int age) { super(); this.name = name; this.age = age; } @Override //转为字符串 public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override //重写hashCode()方法 public int hashCode() { return name.hashCode()+age*31; } @Override //重写equals()方法 public boolean equals(Object obj) { if(this==obj){ return true; } if(obj==null){ return false; } if(obj instanceof Person){ Person p=(Person) obj; return name.equals(p.name)&&age==p.age; } return false; } }
测试类方法:
public static void method3(){ HashSet<Person> set=new HashSet<Person>(); set.add(new Person("a",20)); set.add(new Person("a",10)); set.add(new Person("b",30)); set.add(new Person("b",30)); System.out.println(set); }
运行结果:因为重写了hashCode方法跟equals方法 所以重复的元素并没有存储到集合中
三、LinkedHashSet集合:
我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?
在HashSet下面有一个子类LinkedHashSet,它是链表和哈希表组合的一个数据存储结构。
实例:迭代器遍历有序唯一输出:
//迭代器遍历有序唯一输出 public static void method4(){ LinkedHashSet<String> set=new LinkedHashSet <String>(); set.add("a"); set.add("aabbb"); set.add("张三"); set.add("李四"); set.add("a"); set.add("李四"); Iterator it=set.iterator(); while(it.hasNext()){ System.out.println(it.next()); }
运行结果:按照存储顺序打印
四、 判断集合元素唯一的原理
4.1ArrayList的contains方法判断元素是否重复原理
ArrayList的contains方法会使用调用方法时,传入的元素的equals方法依次与集合中的旧元素所比较,从而根据返回的布尔值判断是否有重复元素。
(true则有,false则无)。此时,当ArrayList存放自定义类型时,由于自定义类型在未重写equals方法前,判断是否重复的依据是地址值,
所以如果想根据内容判断是否为重复元素,需要重写元素的equals方法。
4.2HashSet的add/contains等方法判断元素是否重复原理
Set集合不能存放重复元素,其添加方法在添加时会判断是否有重复元素,有重复不添加,没重复则添加。
HashSet集合由于是无序的,其判断唯一的依据是元素类型的hashCode与equals方法的返回结果。规则如下:
先判断新元素与集合内已经有的旧元素的HashCode值
l 如果不同,说明是不同元素,添加到集合。
l 如果相同,再判断equals比较结果。返回true则相同元素;返回false则不同元素,添加到集合。
所以,使用HashSet存储自定义类型,如果没有重写该类的hashCode与equals方法,则判断重复时,使用的是地址值,如果想通过内容比较元素是否相同,
需要重写该元素类的hashcode与equals方法。