目录
Set
1、Set集合概述及特点:
public interface Set<E> extends Collection<E> 不包含重复元素的集合。 更正式地,集合不包含一对元素e1和e2 ,使得e1.equals(e2) ,并且最多一个空元素。
Set接口继承自Collection接口的所有构造函数(方法)。Set与Collection方法一模一样。
2、Set集合子类如何保证元素唯一?
HashSet
1、Set集合子类HashSet<E>概述:
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, Serializable ,此类实现Set接口,由哈希表(实际上是一个HashMap实例)支持。 它不保证set的迭代顺序; 特别是它不保证该顺序在一段时间内保持不变。此类允许使用null元素。翻译:存取顺序不一致,可以存储null。
2、案例演示:HashSet存储字符串并遍历。
import java.util.HashSet;
/**
* set集合中元素无索引,不可以重复,无序(存取顺序不一致)
*/
public class Demo1_HashSet {
public static void main(String[] args) {
HashSet<String> hs = new HashSet<>(); //创建HashSet对象。
boolean b1 = hs.add("a");
boolean b2 = hs.add("a"); //当向Set集合中存储重复元素的时候返回为false。
System.out.println(hs); //HashSet的继承体系中有重写toString方法
System.out.println(b1); //true
System.out.println(b2); //false
//HashSet的爷爷类AbstractCollection重写了toString()
/*public String toString() {
Iterator<E> it = iterator(); //获取迭代器
if (! it.hasNext()) //如果迭代器中没有元素
return "[]"; //返回[]
StringBuilder sb = new StringBuilder(); //如果有元素,创建StringBuilder
sb.append('['); //将元素不断进行添加
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString(); //最后将元素转换成toString给返回
sb.append(',').append(' ');
}
}*/
System.out.println("----------------------------------");
hs.add("d");
hs.add("c");
hs.add("b");
System.out.println(hs);
for (String string : hs) { //只要能使用迭代器迭代的,就可以使用增强for循环遍历。
System.out.println(string);
}
}
}
3、案例演示:HashSet存储自定义对象,并保证元素唯一性。
import java.util.HashSet;
import com.bean.Person;
public class Demo2_HashSet {
public static void main(String[] args) {
HashSet<Person> hs = new HashSet<>();
hs.add(new Person("张三", 23));
hs.add(new Person("张三", 23));
hs.add(new Person("李四", 24));
hs.add(new Person("李四", 24));
hs.add(new Person("李四", 24));
hs.add(new Person("李四", 24));
//即使重写了自定义类中的equals方法,依然所有元素都在。因为并没有执行。
//重写了hashCode方法后,保证元素唯一了,我们并没有调equals方法,equals方法也跟着执行了。
//集合的每个对象元素进入集合的内存空间时,对象元素会自动调对应自定义类的hashCode()给自己算一个具体的hashCode值(地址值),每个对象的hashCode值都不一样所以不会调equals()进行比较。因为地址值都不一样,所以重复的对象都保留了。
//重写hashCode()给定一个固定的值(任意),不让自己算了。地址值相同的对象元素,调用equals()进行比较,如果元素属性值相同,则返回值是true,就不存了。如果元素属性不同,HashSet有桶结构,新元素会像桶一样挂在被比较的元素上。再进来的元素会先与挂着的元素比较。
System.out.println(hs.size());
System.out.println(hs);
}
}
重写hashCode()和equals()方法
public boolean equals(Object obj) {
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age;
}
public int hashCode() {
return 10;
}
代码优化:
为了减少比较,优化hashCode()代码写法。最终版就是自动生成即可。
Alt + Shift + S + H 自动生成hashCode()和equals()。
//这么复杂是为了少调用equals(),提高程序的运行效率。
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
public boolean equals(Object obj) {
if (this == obj) //传入对象等于调用对象,说明两个引用指向同一个对象。
return true; //直接返回true
if (obj == null) //传入的对象为null
return false; //返回false
if (getClass() != obj.getClass()) //调用的字节码对象和传入的字节码对象不是同一个。内存中同名的字节码文件只能有一个。
return false; //直接返回false。 到这里,判断了传入对象一定是自定义对象,跟被调用对象不是同一个对象,肯定是有值的,和被调用对象属于同一个字节码文件生成的对象。
Person other = (Person) obj; //向下转型 因为上一步进行过判断,所以不会出现类型转换异常。
if (age != other.age) //调用对象的年龄不等于传入对象的年龄
return false; //返回false
if (name == null) { //调用对象的name属性为null,name是自定义对象的属性,是可以赋值为null的(不给赋值),不存在空指针异常。
if (other.name != null) //传入对象的name属性不为null
return false; //返回false
} else if (!name.equals(other.name))//调用对象的姓名不等于传入对象的姓名
return false; //返回false
return true; //返回true
}
prime等于31是因为:
31是一个质数,质数是仅能被1和自己整除的数,这样他的公约数就少了,就可以降低重复的可能性。
31这个数既不大也不小。大了,进行运算之后可能超过int的取值范围,小了,最后相乘的结果可能还是重复的。能不重复尽量不重复。
31这个数好算,2的5次方减1,2向左移动5位再减去一。
4、HashSet如何保证元素唯一性的原理:
1.HashSet原理:
我们使用Set集合都需要去掉重复元素,如果在存储的时候逐个元素调用equals()进行比较,效率较低。哈希算法提高了去重复的效率,降低了使用equals()的次数。
当HashSet调用add()方法存储对象的时候,先调用对象的hashCode()方法得到一个哈希值,然后在集合中查找是否有哈希值相同的对象。
如果没有哈希值相同的对象就直接存入集合。
如果有哈希值相同的对象,就和哈希值相同的对象逐个元素调用equals()进行属性值比较,比较结果为false就存入,为true则不存入。
2.将自定义类的对象存入HashSet去重复: