最近重新研究java基础, 看到了java中Object类的equals()方法。java中比较两个对象相等性有两个方法,一个是==, 另一个是俄quals()方法(当然基本类型不能直接调用,需要包装类包装)。常问的问题就是这两个方法的区别,很多人会说出好几条区别,比如==比的是对象的引用,equals比较的是对象的值。其实这是一种误解,看看Object类中equals()方法的源代码。
public boolean equals(Object obj) {
return (this == obj);
}
就这么简单, 原来equals调用的就是==。那么上述的论断来源于哪儿呢,其实很多人都说得是String类重写的equals方法。
重写equals很简单,以下举一个简单例子来演示一下
class Person{
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
// 省略getter/setter方法
}
然后我们就可以来试验equals和==了
Person p1 = new Person("xg", 29);
Person p2 = new Person("xg", 29);
System.out.println(p1 == p2);
System.out.println(p1.equals(p2));
结果都是false,显然。
那么接下来就是要重写equals了。目前流行的写法如下:
public boolean equals(Object obj){
if (obj == null) {
return false;
}else if(!(obj instanceof Person)){
return false;
}
Person p = (Person)obj;
if(this.getName().equals(p.getName()) && this.getAge() == p.getAge()){
return true;
}else{
return false;
}
}
思路很清晰,不说了
不过我更喜欢另一种简单的写法
public boolean equals(Object obj){
if (obj instanceof Person){
return this.getName().equals(((Person)obj).getName())
&& this.getAge() == ((Person)obj).getAge();
}else {
return false;
}
}
这种写法更加简洁,功能是一样的。
再试验上面的例子
Person p1 = new Person("xg", 29);
Person p2 = new Person("xg", 29);
System.out.println(p1 == p2);
System.out.println(p1.equals(p2));
返回false,true,因为equals被重写了。
到此似乎已经很完美了,其实不然,看到下面的结果就会发现。
Map<Person, String> map = new HashMap<Person, String>();
map.put(new Person("xg", 28), "Person1");
System.out.println(map.get(new Person("xg", 28)));
本以为应该返回Person1的字符串,确返回了一个null,这是怎么回事呢?
让我们看看HashMap中get方法是怎么实现的
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode()); // 获得参数key的hashCode
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) // 比较hashCode,如果相同则返回值
return e.value;
}
return null; // 否则返回null
}
那我们就看一看hashCode吧。
Person p1 = new Person("xg", 29);
Person p2 = new Person("xg", 29);
System.out.println(p1.hashCode() + "----" + p2.hashCode());
我们看到两个完全不同的值
那么好了,根据java的Object规范,当两个对象的equals方法返回true时,这两个对象的hashCode必须返回同样的值。为什么要这么规定呢?相信原因你已经知道了,因为一旦不同,那么这个对象在与HashMap,Hashtable,HashSet一起使用时,一定要有问题。(想象一下如果String类没有重写HashCode那将多么可怕)
那么如何重写hashCode方法呢,这个难度就很大了,目前已知的是,如果能保持不同的对象,返回不同的hashCode值,这就是最好的。原理参见hash算法的实现。
接下来就重写一个
public int hashCode(){
return this.name.length()*37 + this.age;
}
再试验一下例子
Map<Person, String> map = new HashMap<Person, String>();
map.put(new Person("xg", 28), "Person1");
System.out.println(map.get(new Person("xg", 28)));
OK了,结果是Person1。
结论:重写equals一定要重写hashCode。
public boolean equals(Object obj) {
return (this == obj);
}
就这么简单, 原来equals调用的就是==。那么上述的论断来源于哪儿呢,其实很多人都说得是String类重写的equals方法。
重写equals很简单,以下举一个简单例子来演示一下
class Person{
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
// 省略getter/setter方法
}
然后我们就可以来试验equals和==了
Person p1 = new Person("xg", 29);
Person p2 = new Person("xg", 29);
System.out.println(p1 == p2);
System.out.println(p1.equals(p2));
结果都是false,显然。
那么接下来就是要重写equals了。目前流行的写法如下:
public boolean equals(Object obj){
if (obj == null) {
return false;
}else if(!(obj instanceof Person)){
return false;
}
Person p = (Person)obj;
if(this.getName().equals(p.getName()) && this.getAge() == p.getAge()){
return true;
}else{
return false;
}
}
思路很清晰,不说了
不过我更喜欢另一种简单的写法
public boolean equals(Object obj){
if (obj instanceof Person){
return this.getName().equals(((Person)obj).getName())
&& this.getAge() == ((Person)obj).getAge();
}else {
return false;
}
}
这种写法更加简洁,功能是一样的。
再试验上面的例子
Person p1 = new Person("xg", 29);
Person p2 = new Person("xg", 29);
System.out.println(p1 == p2);
System.out.println(p1.equals(p2));
返回false,true,因为equals被重写了。
到此似乎已经很完美了,其实不然,看到下面的结果就会发现。
Map<Person, String> map = new HashMap<Person, String>();
map.put(new Person("xg", 28), "Person1");
System.out.println(map.get(new Person("xg", 28)));
本以为应该返回Person1的字符串,确返回了一个null,这是怎么回事呢?
让我们看看HashMap中get方法是怎么实现的
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode()); // 获得参数key的hashCode
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) // 比较hashCode,如果相同则返回值
return e.value;
}
return null; // 否则返回null
}
那我们就看一看hashCode吧。
Person p1 = new Person("xg", 29);
Person p2 = new Person("xg", 29);
System.out.println(p1.hashCode() + "----" + p2.hashCode());
我们看到两个完全不同的值
那么好了,根据java的Object规范,当两个对象的equals方法返回true时,这两个对象的hashCode必须返回同样的值。为什么要这么规定呢?相信原因你已经知道了,因为一旦不同,那么这个对象在与HashMap,Hashtable,HashSet一起使用时,一定要有问题。(想象一下如果String类没有重写HashCode那将多么可怕)
那么如何重写hashCode方法呢,这个难度就很大了,目前已知的是,如果能保持不同的对象,返回不同的hashCode值,这就是最好的。原理参见hash算法的实现。
接下来就重写一个
public int hashCode(){
return this.name.length()*37 + this.age;
}
再试验一下例子
Map<Person, String> map = new HashMap<Person, String>();
map.put(new Person("xg", 28), "Person1");
System.out.println(map.get(new Person("xg", 28)));
OK了,结果是Person1。
结论:重写equals一定要重写hashCode。