今天我来讲一下Collection的孙子接口HashSet.它们的关系是这个样的:Collection-->Set-->HashSet
HashSet集合有这样的特点:底层的数据结构是哈希表,而且线程不同步。表现出来的结果就是无序而且存入的元素不能重复。接下来我就讲一下为什么hashSet输出的结果是无序,而且是不能重复的。
首先我先写一段简单的代码:
import java.util.*; public class HashSet01{ public static void main(String[] args){ HashSet hs = new HashSet(); hs.add("java01"); hs.add("java02"); hs.add("java03"); hs.add("java03"); hs.add("java04"); Iterator it = hs.iterator(); while(it.hasNext()){ sop(it.next()); } } public static void sop(Object obj){ System.out.println(obj); } }这是结果:java04 java03 java02 java01
可以看出无序,而且java03我输入了两遍,但是输出结果是有一遍。原因是这样的
要说HashSet首先必须说一下哈希算法,先看一段代码:
public class Test01{
public static void main(String[] args){
Student d1 = new Student();
Student d2 = new Student();
System.out.println(d1);
System.out.prinltn(d2);
}
}
class Student{
}
输出的结果是
Student@15db9742 Student@6d06d69c
其实这里默认调用的是该对象的toString()方法。toString()返回的是该对象的内存地址经过哈希算法得出的二进制的值转换为十六进制的值。这是截图
toString底层调用的是该对象的HashCode()方法,而HashCode()则会调用哈希算法来返回一个值,这个值就叫做哈希值,就是上面的那个我们看的不是太懂的一串数字字母组合。但是我们如果把继承的Object中的HashCode()方法重写,返回的结果又会是什么样呢?
public class Test01{
public static void main(String[] args){
Student d1 = new Student();
Student d2 = new Student();
System.out.println(d1);
System.out.prinltn(d2);
}
}
class Student{
public int HashCode(){
return 50;
}
}
结果是:
Student@32 Student@32
解析:因为我们已经重写了Student类继承的Object中的hashCode()方法,让它返回一个固定的值,所以连个对象返回的哈希值是一样的。
接下来我们继续说这个HashSet方法,看这一段代码:
import java.util.*; public class HashSet02{ public static void main(String[] args){ HashSet hs = new HashSet(); hs.add(new Person("java01",10)); hs.add(new Person("java02",11)); hs.add(new Person("java03",12)); hs.add(new Person("java02",11)); //hs.add(new Person("java04",13)); sop(hs.contains(new Person("java02",11)));//true Iterator it = hs.iterator(); while(it.hasNext()){ Person p = (Person)it.next(); sop(p.getName()+"::"+p.getAge()); } } public static void sop(Object obj){ System.out.println(obj); } } class Person{ private String name; private int age; Person(String name,int age){ this.name=name; this.age=age; } public int hashCode(){ System.out.println(this.name+"...hashCode..."); return name.hashCode()+age; } public boolean equals(Object obj){ if(!(obj instanceof Person)){ return false; } Person p = (Person)obj; System.out.println(this.name+"...equals..."+p.name); return this.name.equals(p.name) && this.age==p.age; } public void setName(String name){ this.name=name; } public String getName(){ return name; } public void setAge(int age){ this.age=age; } public int getAge(){ return age; } }这块重写了Person继承的equals方法和hashCode方法,顺序是这样的,HashCode比较的是内存地址,就是哈希值,哈希值一样了,才会调用该对象的equals方法去比较内容。
import java.util.*; public class HashSet02{ public static void main(String[] args){ HashSet hs = new HashSet(); hs.add(new Person("java01",10)); hs.add(new Person("java02",11)); hs.add(new Person("java03",12)); sop(hs.contains(new Person("java02",11)));//true Iterator it = hs.iterator(); while(it.hasNext()){ Person p = (Person)it.next(); sop(p.getName()+"::"+p.getAge()); } } public static void sop(Object obj){ System.out.println(obj); } } class Person{ private String name; private int age; Person(String name,int age){ this.name=name; this.age=age; } public int hashCode(){ System.out.println(this.name+"...hashCode..."); //return 60; return name.hashCode()+age; } public boolean equals(Object obj){ if(!(obj instanceof Person)){ return false; } Person p = (Person)obj; System.out.println(this.name+"...equals..."+p.name); return this.name.equals(p.name) && this.age==p.age; } public void setName(String name){ this.name=name; } public String getName(){ return name; } public void setAge(int age){ this.age=age; } public int getAge(){ return age; } }
输出的结果是:
因为这块我们没有重写hashCode()方法,所以这三个对象输出的哈希值是不一样的,HashSet有个特点就是会比较对象的hashCode方法和equals方法,如果hashCode方法的结果一样,才会调用该对象的equals方法,hashCode方法要是不一样,则不会调用equals方法,这里明显没有调用equals方法
如果这里把hashCode方法重写成这样
public int hashCode(){
return 90;
}
这样的话,输出的结果为:
这块就调用了equals方法了,因为hashCode输出的结果一样,就会继续的调用equals方法。
接下来我再来解释一下为啥HashSet存储的
我们前提假设这几哥对象的内存地址(哈希值)是一样的,看着一段代码;
import java.util.*; public class HashSet02{ public static void main(String[] args){ HashSet hs = new HashSet(); hs.add(new Person("java01",10)); hs.add(new Person("java02",11)); hs.add(new Person("java03",12)); hs.add(new Person("java02",11)); //hs.add(new Person("java04",13)); Iterator it = hs.iterator(); while(it.hasNext()){ Person p = (Person)it.next(); sop(p.getName()+"::"+p.getAge()); } } public static void sop(Object obj){ System.out.println(obj); } } class Person{ private String name; private int age; Person(String name,int age){ this.name=name; this.age=age; } public int hashCode(){ System.out.println(this.name+"...hashCode..."); return 60; //return name.hashCode()+age; } public boolean equals(Object obj){ if(!(obj instanceof Person)){ return false; } Person p = (Person)obj; System.out.println(this.name+"...equals..."+p.name); return this.name.equals(p.name) && this.age==p.age; } public void setName(String name){ this.name=name; } public String getName(){ return name; } public void setAge(int age){ this.age=age; } public int getAge(){ return age; } }
结果是这样的:
底层原理是这样的:java01这个人java02这个人的哈希值是一样的,所以会出来前两个语句,所以就会比较这两个的equals方法。然后再去计算java03的哈希值,和java01,java02的一样,就会分别和java01,java02比较其equals方法,第四条语句和第二天语句一样,依然会计算出哈希值,和java01,java02,java03一样,就会比较equals方法,先和java01比较,不一样,在和java02比较,一样,所以就不会输出了,自然就不会和java03比较了。