一、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方法。