JavaSE 集合类HashSet保证自定义对象唯一性
首先我们自定义Person类,只有姓名和年龄两个属性
class Person{
private String name ;
private int age ;
public Person(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 "[name=" + name + ", age=" + age + "]";
}
}
创建HashSet,元素为Person对象
public class SetDemo1 {
public static void main(String args[]) {
Set<Person> mySet = new HashSet<>();
mySet.add(new Person("唐三",20)) ;
mySet.add(new Person("唐三",20)) ;
mySet.add(new Person("小舞",19)) ;
System.out.println(mySet);
}
}
运行结果:
相同的人存进Set里了,这似乎与Set不存储相同元素的特点相悖。
实质上当执行第一个mySet.add(new Person(“唐三”,20))时,Person对象会被自动分配一个hashcode。执行第二个mySet.add(new Person(“唐三”,20))时Person对象会得到一个不同hashcode,这个hashcode到底从哪来我们可以看下源码
这个map是HashMap,可见Set底层由Map实现,不过这里并没有求hashcode,继续看此put的源码
这里显示求出键key的hash值,也就是我们所存入对象的hash值。有兴趣的可以继续往下看底层实现。
由于hashcode不同,所以第二个Person对象可以加入Set集合。如果两对象的hashcode相同就会调用euqals()方法。我们修改代码的思路应该是覆写hashCode方法,让有相同姓名年龄的Person的hashcode相同,同名同龄为同一个Person对象是我们人为规定的,所以我们还要覆写Person的equals()方法让它知道这个规定。(在Eclipse里通过alt+shift+s+h可以快速添加hashCode和equals方法)
Person类代码修改为:
class Person{
private String name ;
private int age ;
public Person(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;
}
public String toString() {
return "[name=" + name + ", age=" + age + "]";
}
@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) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) 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;
}
}
再次运行SetDemo1结果:
这样就确保了HashSet里自定义对象的唯一性,关键就是覆写自定义对象的hashCode()和equals()。
上述代码中hashCode()和equals()是使用eclipse快速构建的。为什么hashCode()方法要那样写呢?实质上写成
public int hashCode() {
return age+name.hashCode();
}
也可以。但是这样有个弊端就是假设HashSet里有一个Person,名字哈希码是30,年龄20,要加入的Person名字哈希码为29,年龄21,这样就造成虽然两Person对象不同,但后者无法存入。为降低hashcode重复的几率,就将年龄与名字的哈希码进行一定规律的变化。
至于为什么prime是31也有讲究
- 31是一个质数,公约数少。
- 31大小适中。太大的话经过一系列乘加可能超出int取值范围,太小重复几率就高了。
- 31是25-1,好算