集合中,Set里面存储的元素不能重复,没有索引,存取顺序不一致。
Set的继承关系图如下
Set接口继承了Collection接口,
HashSet存放无序,不重复的元素常用方法
boolean add(E e) 添加一个元素,返回一个布尔值,如果添加的元素在Set中存在,则添加不了,返回false
add的实现是先调用对象的hashCode方法,如果返回的hashCode值相同,再调用equals方法,判断添加的元素是否在Set中存在,存在则不添加,不存在则添加.如果返回的hashCode值不同,就直接将元素添加到Set中,所以添加的元素要重写hashCode()和equals()方法
int size() 返回Set的元素个数
void clear() 清空Set中所有元素
boolean contains(Object o) 返回Set中是否包含了特定元素
boolean isEmpty() 检查Set中是否没有元素,没有就返回true,有就返回false
Iterator<E> iterator() 生成Set的迭代器对象
boolean remove(Object o) 在Set中删除特定元素,如果此元素在Set中返回true,如果Set中没有此元素返回false
示例:
import java.util.HashSet;
import java.util.Set;
public class HashSetTest01 {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
boolean b1 = set.add("a");
boolean b2 = set.add("a");
System.out.println(b1);// true
System.out.println(b2);// false
set.add("c");
set.add("f");
set.add("b");
for (String s : set) {
System.out.print(s + " ");
} //打印结果为a b c f 可以看到跟Set中的元素跟存放顺序无关
}
}
out:
true
false
a b c f
hashCode()方法
hashCode方法可以配合基于散列的集合,这样的散列集合一般使用在hash容器中包括HashSet、HashMap以及HashTable.
HashCode方法的作用就是将对象均匀散列分类,然后获取到编号值,
类似于下图,将相同或者相似对象归为一类(hash code相同),再用equals方法判断是否相同
equals方法和hashCode方法 的关系
在Map和Set这种存储不重复元素的容器中,equals方法本来是可以比较出两个对象是否相同,但是如果对象的个数太多的话,会影响性能,所以hashCode()可以先来一个预先判断,将对象hashCode方法返回的hash code存储在hash表中,下个对象hashCode方法返回的hash code如果在hash表中能找到,进行equals比较,如果hash表不存在此hash code, 说明这个对象没有跟以前的重复,不需要进行equals比较
为什么有了hashCode()方法还要equals方法,是因为hashCode()方法并不是完全可靠,不同对象的hash code值可能相同
重写hashCode()方法的必要性 Object类中的hashCode()方法只是返回当前对象的地址,相同的一个类,new创建两个对象,由于他们在内存里的地址不同,则他们的hashCode()不同,这显然达不到我们想要hsahCode()预判断的效果,所以要重写hashCode()
针对Set,Map这种类型的容器,重写equals()方法时也要重写hashCode()方法
重写hashCode方法时要注意:
- 相等的对象(equals比较为true)必须具有相等的散列码(hash code)。
- 不等的对象(equals比较为false),散列码(hash code)值可以相等也可以不相等.
- 任何时候对同一对象多次调用 hashCode 方法,都必须一直返回同样的整数
示例:
package students;
public class Student {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student(int age, String name) {
super();
this.age = age;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student [age=" + age + ", name=" + name + "]";
}
//eclipse自动生成的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;
}
@Override
public boolean equals(Object obj) {
System.out.println("equals");
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
//测试类
import java.util.HashSet;
import java.util.Set;
import students.Student;
public class HashSetTest02 {
public static void main(String[] args) {
Set<Student> set = new HashSet<>();
set.add(new Student(12, "张三"));
set.add(new Student(13, "李四"));
set.add(new Student(14, "赵五"));
set.add(new Student(12, "张三"));
set.add(new Student(13, "李四"));
System.out.println(set); // 如果对象类没有重写hashCode,Set不会去重,会把所有元素添加到Set中
// 对象类重写hashCode方法后,Set就会去重
}
}
out:
equals
equals
[Student [age=14, name=赵五], Student [age=12, name=张三], Student [age=13, name=李四]]
当自定义的类只有int age和String name这两个属性时,eclipse自动生成的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;
}
为什么eclipse自动重写的hashCode方法中的prime为31?
其实这个值改成别的也可以,只不过定义为31之后有一些好处:
- 31是一个质数,质数是能被1和自己本身整除的数,并且这个数不大也不小
- 31这个数好算,2的五次方-1,2向左移动5位