才学java的时候,打印某个对象是打印的一串莫名其妙的数字,后来知道这个是hashcode,就以为hashcode是对象的某个地址信息,直到看了一些文章才发现事实并非如此。
euqlas equals的作用是用来判断两个对象是否相等,定义在Object中,通过两个对象的地址来判断对象是否相等。
类没有覆盖equals方法时 如果类没有覆盖equals方法,如果通过equals比较两个对象,实际上是比较两个对象是不是同一个对象,相当于==
比较
覆盖了equals方法的情况 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 public class ConflictHashCodeTest2 { public static void main (String[] args ) { Person p1 = new Person("eee" , 100 ); Person p2 = new Person("eee" , 100 ); Person p3 = new Person("aaa" , 200 ); Person p4 = new Person("EEE" , 100 ); HashSet set = new HashSet(); set .add (p1); set .add (p2); set .add (p3); System.out .printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n" , p1.equals (p2), p1.hashCode(), p2.hashCode()); System.out .printf("p1.equals(p4) : %s; p1(%d) p4(%d)\n" , p1.equals (p4), p1.hashCode(), p4.hashCode()); System.out .printf("set:%s\n" , set ); } private static class Person { int age; String name; public Person (String name, int age ) { this .name = name; this .age = age; } public String toString () { return name + " - " +age; } public int hashCode () { int nameHash = name.toUpperCase().hashCode(); return nameHash ^ age; } @Override public boolean equals (Object obj ) { if (obj == null ){ return false ; } if (this == obj){ return true ; } if (this .getClass() != obj.getClass()){ return false ; } Person person = (Person)obj; return name.equals (person.name) && age==person.age; } } }
==和equals 如果equals没有被重写,则与==
相同,都是比较两个对象地址是不是相等
hashcode方法 hashcode也是定义在Object中,作用是获取哈希码,它返回了一个整数。哈希码的作用是确定该对象在哈希表中索引的位置。 虽然每个类都有hashcode,但是仅仅某个类的散列表时,该类的hashcode才有用,用来确定该类的某个对象在散列表中的位置,其他情况下hashcode没有作用。
散列码的作用 我们都知道,散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码! 散列表的本质是通过数组实现的。当我们要获取散列表中的某个“值”时,实际上是要获取数组中的某个位置的元素。而数组的位置,就是通过“键”来获取的;更进一步说,数组的位置,是通过“键”对应的散列码计算得到的
散列的碰撞 简单的散列方法就是取余,2%10和12%10这两个产生的键都是一样的,这就是碰撞
链接法处理碰撞 让发生碰撞的数据公用一个地址,可以让数组的每个slot(槽)都指向一个链表。 开放寻址法处理碰撞 让每个数据尽量分散的映射到一些探查序列上,让每个数据使用探查序列中任何一种的可能性相同,就是所谓的一致散列 。 常用的方法:线性探查(按着顺序),二次探查、双重探查
hashcode与equals的关系 当我们往散列表中插入元素时,是通过hashcode找到元素位置,所以有:
两个对象相等,那么hashcode一定相等 hashcode相等,两个对象不一定相等(在同一个槽内) 不会创建散列表的类 指的是不会是hashMap、hashTable、hashSet等这些本质是散列表的数据结构的类 在这种类中,hashcode和equals是没有任何关系的
会创建散列表的类 如果两个对象相等,那么它们的hashCode()值一定相同。这里的相等是指,通过equals()比较两个对象时返回true。 如果两个对象hashCode()相等,它们并不一定相等。因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出键值对相等。补充说一句:“两个不同的键值对,哈希值相等”,这就是哈希冲突。 这种情况下如果要判断两个对象是否相等,需要同时覆盖equals和hashcode方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 public class ConflictHashCodeTest2 { public static void main (String[] args ) { Person p1 = new Person("eee" , 100 ); Person p2 = new Person("eee" , 100 ); Person p3 = new Person("aaa" , 200 ); Person p4 = new Person("EEE" , 100 ); HashSet set = new HashSet(); set .add (p1); set .add (p2); set .add (p3); System.out .printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n" , p1.equals (p2), p1.hashCode(), p2.hashCode()); System.out .printf("p1.equals(p4) : %s; p1(%d) p4(%d)\n" , p1.equals (p4), p1.hashCode(), p4.hashCode()); System.out .printf("set:%s\n" , set ); } private static class Person { int age; String name; public Person (String name, int age ) { this .name = name; this .age = age; } public String toString () { return name + " - " +age; } @Override public int hashCode () { int nameHash = name.toUpperCase().hashCode(); return nameHash ^ age; } public boolean equals (Object obj ) { if (obj == null ){ return false ; } if (this == obj){ return true ; } if (this .getClass() != obj.getClass()){ return false ; } Person person = (Person)obj; return name.equals (person.name) && age==person.age; } } }
可以看到结果如下:
1 2 3 p1.equals (p2) : true; p1 (68545 ) p2 (68545 ) p1.equals (p4) : false; p1 (68545 ) p4 (68545 ) set:[aaa - 200 , eee - 100 ]
参考资料