您的位置:首页 > 移动开发 > Objective-C

重写object中的hashcode和equals方法

2012-11-01 11:04 295 查看
覆盖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(!(o
instanceofCaseInsensitiveString))
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(!(obj
instanceof Point))
return
false
;
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(!(obj
instanceof 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(!(obj
instanceof Point))//1
return
false
;
//obj只是Point的对象
if(!(obj
instanceof ColorPoint))//2
return
super
.equals(obj);
//obj是ColorPoint的对象
return
super
.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(!(obj
instanceof 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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: