首先我们要创建一个HashSet集合的对象。在创建好对象之后,按住Ctrl键点击HashSet<>,进入底层代码。
public HashSet() {
map = new HashMap<>();
}
我们可以看到,HashSet的构造方法中实际上是创建了一个HashMap的对象,传给了map。
1.add底层代码分析
我们按住ctrl点击add进入底层代码
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
会得到这样的一段代码,我们可以看到,实际上HashSet的add方法调用的是HashMap中的add方法。只不过HashSet中所有的Value均为一个定制,变量只有HashMap中的“K”。
继续找到put的源码
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
这个时候,调用在了putVal的方法,传入了五个值,其中后三个是不可改变的,key就是我们所传入的值,第一值调用了hash方法。我们接着找到Hash的源码,在去探究putVal方法。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
我们传入的值就是这里的key,是一个上转型对象,所以在调用方法时,就是多态,所以实际上调用时会调用子类重写父类后的方法,这里我们传入的类型为String类型,也就是在创建集合时的泛型我们规定为了String型(在后面我们还要讲解到非String类型)算这里是一个三目运符,我们需要知道的是每一次传入值我们的这个返回值会不会发生变化即可,这里又调用了HashCode方法,我们继续点进去一探究竟。
public native int hashCode();
下面来讲解hashCode方法。
2.hashCode方法
我们观察如下代码:
package test;
import java.util.HashSet;
public class Test {
public static void main(String[] args) {
Student stu1=new Student();
System.out.println(stu1.hashCode());
System.out.println(stu1);
System.out.println(Integer.toHexString(stu1.hashCode()));
}
}
执行结果:
5433634
test.Student@52e922
52e922
我们可以看到,hashCode执行结果出来的是一串数字,具体不知道用来干什么的。
第二个输出语句打印了对象的地址,神奇的事情来了,第三个输出语句方法为将十六进制转换成二进制,我们发现将hashCode打印出来的数字转换成二进制后刚好为对象的地址。
我们不难的出结论,hashCode方法与地址息息相关,每一个对象调用hashCode方法所打印的值都是相同的。
例外:
package test;
import java.util.HashSet;
public class Test {
public static void main(String[] args) {
String name1="Tom";
String name2=new String("Tom");
System.out.println(name1.hashCode());
System.out.println(name2.hashCode());
System.out.println(name1==name2);
}
}
执行结果:
84274
84274
false
我们发现name1和name2的地址是不同的,这一点通过“==”就可以判定。但是为什么他们两个对象调用hashCode方法输出的值是同一个值呢?
因为在String类中重写了hashCode方法,至于如何重写的我们不需要知道。只需要知道因为重写,即使是两个地址不同的对象,如果字符串内容相同,那么调用hashCode方法时仍然会输出相同的值!
经过讲解我们可以知道,如果两个字符串相同的String类型数据传入时,他们的hash值是相同的。
知道了这些之后我们正式进入putVal方法的讨论
3.putVal方法
putVal方法的源码如下,我们边读代码边看注释,无论是什么类型的数据,第一次进入putVal时的过程都是如下
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,