一、Set接口介绍
学习Collection接口时,记得Collection中可以存放重复元素,也可以不存放重复元素,那么我们知道List中是可以存放重复元素的。那么不重复元素给哪里存放呢?
那就是Set接口,它里面的集合,所存储的元素就是不重复的。
查阅Set集合的API介绍,通过元素的equals方法,来判断是否为重复元素,它是个不包含重复元素的集合。Set集合取出元素的方式可以采用:迭代器、增强for。
Set集合有多个子类,这里我们介绍其中的HashSet、LinkedHashSet这两个集合。
二、HashSet集合
查阅HashSet集合的API介绍:此类实现Set接口,由哈希表支持(实际上是一个 HashMap集合),其底层 HashMap 实例的默认初始容量是 16,加载因子是 0.75。
HashSet集合不能保证的迭代顺序与元素存储顺序相同。
HashSet集合,采用哈希表结构存储数据,保证元素唯一性的方式依赖于:hashCode()与equals()方法。
构造方法:
常用方法:
1. HashSet集合存储数据的结构(哈希表)
什么是哈希表呢?
哈希表底层使用的也是数组机制,数组中也存放对象,而这些对象往数组中存放时的位置比较特殊,当需要把这些对象给数组中存放时,那么会根据这些对象的特有数据结合相应的算法,计算出这个对象在数组中的位置,然后把这个对象存放在数组中。而这样的数组就称为哈希数组,即就是哈希表。
当向哈希表中存放元素时,需要根据元素的特有数据结合相应的算法,这个算法其实就是Object类中的hashCode方法。由于任何对象都是Object类的子类,所以任何对象都拥有这个方法。即就是在给哈希表中存放对象时,会调用对象的hashCode方法,算出对象在表中的存放位置,这里需要注意,如果两个对象hashCode方法算出结果一样,这样现象称为哈希冲突,这时会调用对象的equals方法,比较这两个对象是不是同一个对象,如果equals方法返回的是true,那么就不会把第二个对象存放在哈希表中,如果返回的是false,就会把这个值存放在哈希表中。
总结:保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。
2. 重写equals()方法和 hashCode()方法
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。
equals() 的作用是 用来判断两个对象是否相等,通过判断两个对象的地址是否相等(即,是否是同一个对象)来区分它们是否相等。它一般有两种使用情况:
- 情况1,类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
- 情况2,类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。
讲到这里,顺便说一下java对equals()的要求。有以下几点:- 对称性:如果x.equals(y)返回是"true",那么y.equals(x)也应该返回是"true"。
- 反射性:x.equals(x)必须返回是"true"。
- 类推性:如果x.equals(y)返回是"true",而且y.equals(z)返回是"true",那么z.equals(x)也应该返回是"true"。
- 一致性:如果x.equals(y)返回是"true",只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是"true"。
- 非空性,x.equals(null),永远返回是"false";x.equals(和x不同类型的对象)永远返回是"false"。
java中判断两个对象是否相等的规则:
首先,判断两个对象的hashCode是否相等
如果不相等,认为两个对象也不相等
如果相等,则判断两个对象用equals运算是否相等
如果不相等,认为两个对象也不相等
如果相等,认为两个对象相等
equals()相等的两个对象,hashcode()一定相等;
equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。
在Object类中,equls()方法和==的本质是相同的,都是判断两个引用是否指向同一对象。
java中很多类都重写了equals方法,供自己产生新的定义。如String类就重写了equals()方法,使得equals()方法的作用是比较两个字符串的内容是否相等。
实例:Items类继承自Object类,重写equals()方法,不按照父类的比较方法来比较两个对象,使得该方法能够按照程序员自己的意愿来比较两个对象。
@Items.java//解决编号没有合并问题
@Override
public int hashCode() {
//若getId和getName相同,则hashCode一定相同
return this.getId()+this.getName().hashCode();
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
if(this==obj)
{
return true;
}
if(obj instanceof Items)
{
Items i = (Items)obj;
if(this.getId()==i.getId()&&this.getName().equals(i.getName()))
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
3. HashSet存储JavaAPI中的类型元素
给HashSet中存储JavaAPI中提供的类型元素时,不需要重写元素的hashCode和equals方法,因为这两个方法,在JavaAPI的每个类中已经重写完毕,如String类、Integer类等。
- 创建HashSet集合,存储String对象。
public class HashSetDemo {
public static void main(String[] args) {
//创建HashSet对象
HashSet hs = new HashSet();
//给集合中添加String对象
hs.add("zhangsan");
hs.add("lisi");
hs.add("wangwu");
hs.add("zhangsan");
//取出集合中的每个元素
Iterator it = hs.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
}
}
输出结果如下,说明集合中不能存储重复元素:
wangwu
lisi
zhangsan
4. HashSet存储自定义类型元素
给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一
- 创建自定义对象Student
public class Student {
private String name;
private int age;
public Student(String name, int age) {
super();
this.name = name;
this.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
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
//进行hashcode方法的重写
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
//进行equals方法的重写
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if(!(obj instanceof Student)){
System.out.println("类型错误");
return false;
}
Student other = (Student) obj;
return this.age == other.age && this.name.equals(other.name);
}
}
- 创建HashSet集合,存储Student对象。
public class HashSetDemo {
public static void main(String[] args) {
//创建HashSet对象
HashSet hs = new HashSet();
//给集合中添加自定义对象
hs.add(new Student("zhangsan",21));
hs.add(new Student("lisi",22));
hs.add(new Student("wangwu",23));
hs.add(new Student("zhangsan",21));
//取出集合中的每个元素
Iterator it = hs.iterator();
while(it.hasNext()){
Student s = (Student)it.next();
System.out.println(s);
}
}
}
输出结果如下,说明集合中不能存储重复元素:
Student [name=lisi, age=22]
Student [name=zhangsan, age=21]
Student [name=wangwu, age=23]
三、 LinkedHashSet介绍
我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?
在HashSet下面有一个子类LinkedHashSet,它是链表和哈希表组合的一个数据存储结构。
演示代码如下:
public class LinkedHashSetDemo {
public static void main(String[] args) {
Set set = new LinkedHashSet();
set.add("bbb");
set.add("aaa");
set.add("abc");
set.add("bbc");
Iterator it = set.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
输出结果如下,LinkedHashSet集合保证元素的存入和取出的顺序:
bbb
aaa
abc
bbc
注意,LinkedHashSet不是同步的,即线程不安全。如果需要它变成线程安全的,则需用Collections.synchronizedSet
方法来“包装”该 set。最好在创建时完成这一操作,以防止意外的非同步访问:
Set s = Collections.synchronizedSet(new LinkedHashSet(...));
四、判断结合元素是否重复的原理
1. ArrayList的contains方法判断元素是否重复原理
ArrayList的contains方法会使用调用方法时,传入的元素的equals方法依次与集合中的旧元素所比较,从而根据返回的布尔值判断是否有重复元素。此时,当ArrayList存放自定义类型时,如果自定义类型未重写equals方法,判断是否重复的依据是地址值,所以如果想根据内容判断是否为重复元素,需要重写元素的equals方法。
2. HashSet的add/contains等方法判断元素是否重复原理
Set集合不能存放重复元素,其添加方法在添加时会判断是否有重复元素,有重复不添加,没重复则添加。
HashSet集合由于是无序的,其判断唯一的依据是元素类型的hashCode与equals方法的返回结果。规则如下:
先判断新元素与集合内已经有的旧元素的HashCode值
- 如果不同,说明是不同元素,添加到集合。
- 如果相同,再判断equals比较结果。返回true则相同元素;返回false则不同元素,添加到集合。
所以,使用HashSet存储自定义类型,如果没有重写该类的hashCode与equals方法,则判断重复时,使用的是地址值,如果想通过内容比较元素是否相同,需要重写该元素类的hashcode与equals方法。