覆盖equals方法
1. Object中对equals、hashcode方法的实现
(1) 在object中直接用 ==比较引用是不是指向同一个对象
(2) 直接返回对象在内存中的地址
2. 什么情况下不用覆盖equals方法
(1) 一个类的实例在本质上都是不同的。比如说Thread对象,每个Thread对象都是不同的。
(2) 不用去关心一个类的两个对象是不是逻辑上是相等的。
(3) 超类中已经覆盖了equals方法,而在子类中从超类继承过来的equals方法对于子类来说也是适用的。
(4) 私有类或者package类,这些类能保证外部不会调用equals方法,所以不需要去重写。
3. 什么情况下需要覆盖equals方法
(1) 逻辑上需要比较相等的类的对象,比如说double、float、PhoneNumber等。
这些类,我们归结为需要在程序中比较逻辑上相等性的value类。
(2) 需要作为key键值放入HashMap,HashSet,HashTable中的类的对象。因为这Hash集合中需要比较类的相等性。这时候不仅需要覆盖equals方法,还需要覆盖HashCode方法
4. 为什么要覆盖equals方法
(1) 逻辑上必须要比较类对象的相等性。
(2) 需要把对象放在Hash集合中。
5. 覆盖equals方法需要遵循的原则
5.1 覆盖equals后,对象必须要满足的特性
(1) 自反性
(2) 对称性
x.equals(y)为true,那么y.equals(x)也一定是true
(3) 传递性
x.equals(y)为true,y.equals(z)为true,那么x.equals(z)也要true
(4) 一致性
(5) 对于null来说,x.equals(null)一定要false
5.2 对称性
package equal.and.hashcode;
import java.util.ArrayList;
import java.util.List;
public class CaseInsensitiveString {
private final Strings;
publicCaseInsensitiveString(String s) {
if (s ==null)
thrownew NullPointerException();
this.s = s;
}
// Broken - violatessymmetry!
@Override
/**
* 违法对称性
* 如果x.equals(y) ==true;那么y.equals(x)也要true
*/
public booleanequals(Object o) {
if (oinstanceof CaseInsensitiveString)
returns.equalsIgnoreCase(((CaseInsensitiveString) o).s);
if (oinstanceof String) // One-way interoperability!
returns.equalsIgnoreCase((String) o);
returnfalse;
}
public static voidmain(String[] args){
CaseInsensitiveStringcis = new CaseInsensitiveString("Hua");
String s ="hua";
System.out.println(s.equals(cis));
//为什么是true,因为CaseInsensitiveStringequals方法直接用内部域
String s来判断;而在String 类中,根本不知道有CaseInsensitiveString
System.out.println(cis.equals(s));
}
}
修正:
public boolean equals(Object o) {
if(!(oinstanceofCaseInsensitiveString))
return false;
CaseInsensitiveStringother = (CaseInsensitiveString) o;
return other.s ==s;
}
5.3 传递性
(1) 第一种情况,子类不覆盖父类的equals方法
这种情况下,相当于子类中新添加的域z不在比较。只要他们位置x,y是相同的,就表示逻辑上是相同的。
父类:
packageequal.and.hashcode;
public class Point {
private int x;
private int y;
public Point(int x,int y){
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object obj) {
if(!(objinstanceof Point))
returnfalse;
Point p =(Point) obj;
return p.x ==x && p.y ==y;
}
}
子类:
packageequal.and.hashcode;
public class ColorPoint extends Point{
private int z;
public ColorPoint(int x,int y,int z) {
super(x, y);
this.z = z;
}
public static void main(String[]args){
ColorPoint c1 = new ColorPoint(1,1,2);
Point p = new Point(1,1);
ColorPoint c2 = new ColorPoint(1,1,6);
//违反对称性
System.out.println(c1.equals(p));
System.out.println(p.equals(c2));
System.out.println(c1.equals(c2));
}
}
(2) 第一种情况,子类覆盖equals方法
情况1:
public boolean equals(Object obj) {
if(!(objinstanceof ColorPoint ))
return false;
return super.equals(obj) && ((ColorPoint) obj).z ==z;
}
比较:
ColorPoint c1 = new ColorPoint(1,1,2);
Point p = new Point(1,1);
//违反对称性
System.out.println(c1.equals(p)); --调用子类的equals,返回false
System.out.println(p.equals(c2)); --调用父类的equals,返回true
情况2:
@Override
public boolean equals(Object obj){
//obj不是Point的对象
if(!(objinstanceof Point))//1
returnfalse;
//obj只是Point的对象
if(!(objinstanceof ColorPoint))//2
returnsuper.equals(obj);
//obj是ColorPoint的对象
returnsuper.equals(obj)&& ((ColorPoint) obj).z ==z;//3
}
用equals比较:
ColorPoint c1 = new ColorPoint(1,1,2);
Point p = new Point(1,1);
ColorPoint c2 = new ColorPoint(1,1,6);
//违反了传递性
System.out.println(c1.equals(p));--调用子类的equals,走到2
System.out.println(p.equals(c2));--调用父类的equals
System.out.println(c1.equals(c2));--调用子类的方法,走到3
最后,发现违法了传递性
继承一个父类时,既想在子类中添加新的域,又想保证equals约定,实现上很困难的。
修正:
利用组合能很容易的修正:
package equal.and.hashcode;
import java.util.HashMap;
import java.util.Map;
public class ColorPoint2 {
private Point point;
private int z;
public ColorPoint2(Point p,intz){
this.point = p;
this.z = z;
}
@Override
public boolean equals(Object obj){
if(!(obj instanceofColorPoint2))
return false;
ColorPoint2 other =(ColorPoint2) obj;
returnother.point.equals(point) && other.z == z;
}
public static void main(String[]args){
ColorPoint2 c1 = newColorPoint2(new Point(1,1),3);
ColorPoint2 c2 = newColorPoint2(new Point(1,1),3);
ColorPoint2 c3 = newColorPoint2(new Point(1,1),3);
System.out.println(c1.equals(c2));
System.out.println(c2.equals(c3));
System.out.println(c1.equals(c3));
}
}
6. 覆盖equals的步骤:
(1) 用==判断是不是指向同一个对象
(2) 用instanceof判断参数是不是正确的类型
(3) 强制转换成正确的类型
(4) 用逻辑上判断相等的关键域来判断相等。
Float用floatToIntBit转换成int在比较
Double用doubleToLongBit转换成long在用==比较
对于引用要把关键域一一做比较
对于数组要把数组中的每个元素做比较
对于Null的处理
(field == o.field || (field != null &&field.equals(o.field)))
覆盖完equals方法后,一定要覆盖hashcode方法
Hashcode覆盖
1. Hashcode的作用
1.1 hashCode()是用来产生哈希玛的,而哈希玛是用来在散列存储结构中确定对象的存储地址的。
1.2 Object中默认的equals和HashCode实现
(1) 默认情况下equals比较引用是不是指向同一个对象
(2) 默认情况下HashCode返回对象内存地址的整数值
对象需要比较逻辑上相等的话,需要重写equals和HashCode方法。
重写hashcode是为了产生哈希玛,而哈希玛是用来在散列存储结构中确定对象的存储地址的。比如hashSet,hashMap这类结构。
如果两个对象hashcode相等,那么需要比较equals。
1.3 HashSet中的hashCode和equals
在将对象存入HashSet中时,通过被存入对象的 hashCode()来确定对象在 HashSet中的存储地址,通过equals()来确定存入的对象是否重复。如果不去重写equals和hashcode方法的话,即使两个对象内容相同,但是hashcode一定不同,equals比较这两个对象一定不同。也就说,在HashSet中保存了逻辑上相同的两个对象。
所以需要存入HashSet并且需要比较逻辑上相等的两个对象需要同时重写equals和hashcode方法。
需满足以下条件:
Equals比较两个对象相等的,他们的hashcode也相同。
存:
满足这个条件在向HashSet中存放对象的时候,根据对象的hashcode定位到应该把这个对象放在HastSet的那个位置,如果这个位置没有对象,直接存放。如果有对象需要用equals比较。
取:
从hashset中取对象的时候,直接根据hashcode就能定位到HashSet保存这个对象的位置,不需要每次都去一一用equals方法比较对象是不是相等了。
Java中对HashSet是用HashMap来实现的。
1.4 HashMap中的hashCode和equals
HashMap中根据key的hashCode来保存value。
hashCode()方法使用来提高Map里面的搜索效率的,Map会根据不同的hashCode()来放在不同的桶里面,Map在搜索一个对象的时候先通过 hashCode()找到相应的桶,然后再根据equals()方法找到相应的对象.要正确的实现Map里面查找元素必须满足一下两个条件:
(1)当obj1.equals(obj2)为true时obj1.hashCode()== obj2.hashCode()必须为true
(2)当obj1.hashCode() == obj2.hashCode()为false时obj.equals(obj2)必须为false
2. 什么情况下需要覆盖hashcode
逻辑上需要比较相等性的类,需要实现。
3. 覆盖hashcode的原则
原则:1
Equals比较相等的两个对象,hashcode要相同。
举个例子来说明:
public final class PhoneNumber {
private final int areaCode;
private final int exchange;
private final int extension;
public PhoneNumber(int a,int exc,int ext) {
this.areaCode = a;
this.exchange = exc;
this.extension = ext;
}
@Override
public boolean equals(Object obj) {
if(obj ==this)
return true;
if(!(objinstanceof PhoneNumber))
return false;
PhoneNumber p = (PhoneNumber) obj;
return p.areaCode ==areaCode && p.exchange ==exchange && extension == p.extension;
}
PhoneNumber类中重写了equals方法,但是没有去重写hashcode方法,导致在HashMap中行为不确定。如下
PhoneNumber p = new PhoneNumber(1,2,3);
Map<PhoneNumber,String> map = newHashMap<PhoneNumber, String>();
map.put(p, "hua");
System.out.println(map.get(new PhoneNumber(1,2,3)));--取出的是null
这是因为PhoneNumber类没有重写hashcode方法,即使两个对象在逻辑上是相等的,但是没有重写hashcode方法,导致这两个对象的hashcode不同,然后hashmap根据key的hashcode去查找value,当然找不到对应的value了。
如果这么实现hashcode行吗?
Public int hashCode(){
Return 42;
}
答案是不行,因为这样的话,保存到散列表中后,所有对象都保存在同一个桶中,hashMap退化为链表,性能从线下变成o2了。
所以实现hashcode,要尽量保证不相等的对象又不同的hashcode
原则2:
逻辑上不相等的对象的hashcode尽量要不同。如果相同的话,保存到散列表后,散列表的性能会下降。
在某个桶中,先根据对象的hashcode找到对象在散列表中的index位置;如果散列表的这个位置只有一个对象,那么直接返回;如果有两个对象以上对象,那么需要在这个位置的链表结构中用对象的equals方法一个一个比较对象。
4. 覆盖hashcode的步骤
不多说,直接看effective java。