equals()和HashCode()深入理解以及Hash算法原理
1.深入理解equals():
- 在我的一篇博客“==”和.equals()的区别中向读者提出提醒: Object类中的equals方法和“==”是一样的,没有区别,即俩个对象的比较是比较他们的栈内存中存储的内存地址。而String类,Integer类等等一些类,是重写了equals方法,才使得equals和“==不同”,他们比较的是值是不是相等。所以,当自己创建类时,自动继承了Object的equals方法,要想实现不同的等于比较,必须重写equals方法。
- 我们看下面这个例子:
package cn.galc.test; public class TestEquals { public static void main(String[] args) { /** * 这里使用构造方法Cat()在堆内存里面new出了两只猫, * 这两只猫的color,weight,height都是一样的, * 但c1和c2却永远不会相等,这是因为c1和c2分别为堆内存里面两只猫的引用对象, * 里面装着可以找到这两只猫的地址,但由于两只猫在堆内存里面存储在两个不同的空间里面, * 所以c1和c2分别装着不同的地址,因此c1和c2永远不会相等。 */ Cat c1 = new Cat(1, 1, 1); Cat c2 = new Cat(1, 1, 1); System.out.println("c1==c2的结果是:"+(c1==c2));//false System.out.println("c1.equals(c2)的结果是:"+c1.equals(c2));//false } } class Cat { int color, weight, height; public Cat(int color, int weight, int height) { this.color = color; this.weight = weight; this.height = height; } }
画出内存分析图分析c1和c2比较的结果,当执行Cat c1 = new Cat(1,1,1); Cat c2 = new Cat(1,1,1);
之后内存之中布局如下图:
- 由此我们看出,当我们new一个对象时,将在内存里加载一份它自己的内存,而不是共用!对于static修饰的变量和方法则保存在方法区中,只加载一次,不会再多copy一份内存。
- 所以我们在判断俩个对象逻辑上是否相等,即对象的内容是否相等不能直接使用继承于Object类的equals()方法,我们必须得重写equals()方法,改变这个方法默认的实现。下面在Cat类里面重写这个继承下来的equals()方法:
class Cat { int color, weight, height; public Cat(int color, int weight, int height) { this.color = color; this.weight = weight; this.height = height; } /** * 这里是重写相等从Object类继承下来的equals()方法,改变这个方法默认的实现, * 通过我们自己定义的实现来判断决定两个对象在逻辑上是否相等。 * 这里我们定义如果两只猫的color,weight,height都相同, * 那么我们就认为这两只猫在逻辑上是一模一样的,即这两只猫是“相等”的。 */ public boolean equals(Object obj){ if (obj==null){ return false; } else{ /** * instanceof是对象运算符。 * 对象运算符用来测定一个对象是否属于某个指定类或指定的子类的实例。 * 如果左边的对象是右边的类创建的对象,则运算结果为true,否则为false。 */ if (obj instanceof Cat){ Cat c = (Cat)obj; if (c.color==this.color && c.weight==this.weight && c.height==this.height){ return true; } } } return false; } }
设计思路很简单:先判断比较对象是否为null—>判断比较对象是否为要比较类的实例—–>比较俩个成员变量是否完全相等。
//另外一种常用重写方法 @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; People other = (People) obj; if (age != other.age) return false; if (firstName == null) { if (other.firstName != null) return false; } else if (!firstName.equals(other.firstName)) return false; if (lastName == null) { if (other.lastName != null) return false; } else if (!lastName.equals(other.lastName)) return false; return true; }
- 这样通过在类中重写equals()方法,我们可以比较在同一个类下不同对象是否相等了。
2.Hash算法原理以及HashCode深入理解
- Java中的Collection有两类,一类是List,一类是Set。List内的元素是有序的,元素可以重复。Set元素无序,但元素不可重复。要想保证元素不重复,两个元素是否重复应该依据什么来判断呢?用Object.equals方法。但若每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说若集合中已有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。于是Java采用了哈希表的原理。
- 当Set接收一个元素时根据该对象的内存地址算出hashCode,看它属于哪一个区间,再这个区间里调用equeals方法。【特别注意】这里需要注意的是:当俩个对象的hashCode值相同的时候,Hashset会将对象保存在同一个位置,但是他们equals返回false,所以实际上这个位置采用链式结构来保存多个对象。
上面方法确实提高了效率。但一个面临问题:若两个对象equals相等,但不在一个区间,因为hashCode的值在重写之前是对内存地址计算得出,所以根本没有机会进行比较,会被认为是不同的对象。所以Java对于eqauls方法和hashCode方法是这样规定的:
1 如果两个对象相同,那么它们的hashCode值一定要相同。也告诉我们重写equals方法,一定要重写hashCode方法,也就是说hashCode值要和类中的成员变量挂上钩,对象相同–>成员变量相同—->hashCode值一定相同。
2 如果两个对象的hashCode相同,它们并不一定相同,这里的对象相同指的是用eqauls方法比较。