站稳马步——(1)重写 equals 和 hashCode 方法
来源:互联网 发布:mac如何显示隐藏文件 编辑:程序博客网 时间:2024/05/22 04:26
站稳马步
——整理Object类(为什么重写equals和hashCode方法)
一. 关键字:
Object、equals()、hashCode()
二. 为什么需要重写:
众所周知,Object是所有类的父类。但,我们在实际开发中自定义自己类时,往往需要重写Object中equals和hashCode方法。为什么呢?首先看看Object的API吧。
Object类中原始写法是:
public boolean equals(Object obj) {
return (this == obj);
}
可见,原始equals比较的是2个对象的“内存地址”。但,我们往往是需要判断的是“逻辑上的内容”是否相等,如:String、Integer、Math...等等,时而我们关心是逻辑内容上是否相等,而不关心是否指向同一对象,所以所要重写。
再者,尽管Object是一个具体的类,但是设计它主要是为了扩展。它所要的非final方法(equals hashCode toString clone和finalize)都有通用约定(general contract),因为它们被设计成要被覆盖(override)的。任何一个类,它在覆盖这些方法的时候,都有责任遵守这些通用的约定;如果不能做到这一点,其它依赖这些约定的类(例如HashMap和HashSet)就无法结合该类一起正常运行。
JDKAPI上重写equals约定如下:
自反性:
对于任何非空引用值 x,x.equals(x) 都应返回 true。
对称性:
对于任何非空引用值 x 和 y,当且仅当y.equals(x) 返回 true 时,x.equals(y)才应返回 true。
传递性:
对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
一致性:
对于任何非空引用值 x 和 y,多次调用x.equals(y) 始终返回 true 或始终返回false,前提是对象上 equals 比较中所用的信息没有被修改。
对于任何非空引用值 x,x.equals(null) 都应返回 false
同时,API规定“当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码“所以也要重写hashCode方法。
public native int hashCode();
说明是一个本地方法,它的实现是根据本地机器相关的,方法返回的是对象的地址值。时而重写hashCode一是为了遵守API约定,二是重点提高对象比较时效率。
因为,在java集合对象中比较对象是这样的,如HashSet中是不可以放入重复对象的,那么在HashSet中又是怎样判定元素是否重复的呢?让我们看看源代码(首先要知道HashSet内部实际是通过HashMap封装的):
public boolean add(Object o) {//HashSet的add方法
return map.put(o, PRESENT)==null;
}
public Object put(Object key, Objectvalue) {//HashMap的put方法
Object k =maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
for (Entry e = table[i]; e != null; e = e.next) {
if (e.hash == hash && eq(k, e.key)) {//从这里可见先比较hashcode
Object oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, k, value, i);
return null;
}
所以在java的集合中,判断两个对象是否相等的规则是:
1,判断两个对象的hashCode是否相等
如果不相等,认为两个对象也不相等,完毕
如果相等,转入2
2,判断两个对象用equals运算是否相等
如果不相等,认为两个对象也不相等
如果相等,认为两个对象相等
为什么是两条准则,难道用第一条不行吗?不行,因为 hashCode()相等时,equals()方法也可能不等,所以必须用第2条准则进行限制,才能保证加入的为非重复元素。
三. 例子:
1.首先
class Student{
String name;
int age;
Student(String name,int age){
this.name=name;
this.age=age;
}
//没有重写equals和hashCode
}
/**
* @author ydj
* @version Apr 28, 2010 3:12:42 PM
*/
public class OverEqualsHashcodeTest {
public static void main(String []args){
Set<Student> set=new HashSet<Student>();
Student stu1=new Student("ydj",26);
Student stu2=new Student("ydj",26);
set.add(stu1);
set.add(stu2);
System.out.println("set.size():"+set.size());
}
}
结果是:2.(这个无须解释)
2.现在重写equals方法如下:
public boolean equals(Object obj){
System.out.println("--------equals()-----------:"+obj);
if(obj==null){
return false;
}
if(!(obj instanceof Student)){
return false;
}else {
Student oth=(Student)obj;
return this.age==oth.age&&this.name==oth.name;
}
// return true;
}
结果是:2.(为什么依然是2呢?!为什么连equals方法都没调用呢)
分析:这就是为什么要重写hashCode的原因(相等对象必须具有相等的哈希码)。因为现在的hashCode依然返回各自对象的地址,就是说明此时的hashCode肯定不相等,故根本不会调用equals()。
3.重写hashCode方法如下:
public int hashCode(){
int res=17;
res=31*res+age;
res=31*res+name.hashCode();
return res;
}
结果是:1.
如果这样重写hashCode:
public int hashCode(){
int res=(int)(Math.random()*100);
return res;
}
这样的话,就等于没有重写了。
四. 设计equals()和hashCode():
A.设计equals()
[1]使用instanceof操作符检查“实参是否为正确的类型”。
[2]对于类中的每一个“关键域”,检查实参中的域与当前对象中对应的域值。
[2.1]对于非float和double类型的原语类型域,使用==比较;
[2.2]对于对象引用域,递归调用equals方法;
[2.3]对于float域,使用Float.floatToIntBits(afloat)转换为int,再使用==比较;
[2.4]对于double域,使用Double.doubleToLongBits(adouble) 转换为int,再使用==比较;
[2.5]对于数组域,调用Arrays.equals方法。
B.设计hashCode()
[1]把某个非零常数值,例如17,保存在int变量result中;
[2]对于对象中每一个关键域f(指equals方法中考虑的每一个域):
[2.1]boolean型,计算(f ? 0 : 1);
[2.2]byte,char,short型,计算(int);
[2.3]long型,计算(int) (f ^ (f>>>32));
[2.4]float型,计算Float.floatToIntBits(afloat);
[2.5]double型,计算Double.doubleToLongBits(adouble)得到一个long,再执行[2.3];
[2.6]对象引用,递归调用它的hashCode方法;
[2.7]数组域,对其中每个元素调用它的hashCode方法。
[3]将上面计算得到的散列码保存到int变量c,然后执行 result=37*result+c;
[4]返回result。
例子:
class Unit {
private short ashort;
private char achar;
private byte abyte;
private boolean abool;
private long along;
private float afloat;
private double adouble;
private Unit aObject;
private int[] ints;
private Unit[] units;
public boolean equals(Object o) {
if (!(o instanceof Unit))
return false;
Unit unit = (Unit) o;
return unit.ashort == ashort
&& unit.achar == achar
&& unit.abyte == abyte
&& unit.abool == abool
&& unit.along == along
&& Float.floatToIntBits(unit.afloat) == Float
.floatToIntBits(afloat)
&& Double.doubleToLongBits(unit.adouble) == Double
.doubleToLongBits(adouble)
&& unit.aObject.equals(aObject) && equalsInts(unit.ints)
&& equalsUnits(unit.units);
}
private boolean equalsInts(int[] aints) {
return Arrays.equals(ints, aints);
}
private boolean equalsUnits(Unit[] aUnits) {
return Arrays.equals(units, aUnits);
}
public int hashCode() {
int result = 17;
result = 31 * result + (int) ashort;
result = 31 * result + (int) achar;
result = 31 * result + (int) abyte;
result = 31 * result + (abool ? 0 : 1);
result = 31 * result + (int) (along ^ (along >>> 32));
result = 31 * result + Float.floatToIntBits(afloat);
long tolong = Double.doubleToLongBits(adouble);
result = 31 * result + (int) (tolong ^ (tolong >>> 32));
result = 31 * result + aObject.hashCode();
result = 31 * result + intsHashCode(ints);
result = 31 * result + unitsHashCode(units);
return result;
}
private int intsHashCode(int[] aints) {
int result = 17;
for (int i = 0; i < aints.length; i++)
result = 31 * result + aints[i];
return result;
}
private int unitsHashCode(Unit[] aUnits) {
int result = 17;
for (int i = 0; i < aUnits.length; i++)
result = 31 * result + aUnits[i].hashCode();
return result;
}
}
为什么要用31这个数呢?因为它是个奇素数。如果乘以偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于移位运算。使用素数效果不是很明显,但是习惯上都是使用素数计算散列结果。31有个好处的特性,即用移位代替乘法,可以得到更好的性能:31*I = =(I << 5)- I。现代的VM可以自动完成这样优化。——《Effctive java SE》
五. 注意:
1.equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。
换句话说,equals()方法不相等的两个对象,hashcode()有可能相等
2.hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。
六. 参考:
1. http://www.cnjm.net/tech/article4731.html
2. http://zhangjunhd.blog.51cto.com/113473/71571
- 站稳马步——(1)重写 equals 和 hashCode 方法
- 站稳马步——(2)clone(克隆)、Cloneable
- 站稳马步——(4)String/StringBuffer/StringBuilder
- 站稳马步——(9)java异常处理
- 站稳马步——(3)java比较器——Comparable和Comparator
- 重写hashCode和equals方法
- 重写equals和hashCode方法
- 重写hashCode和equals方法
- 重写hashcode和equals方法
- 重写equals() 和 hashCode()方法
- 重写hashCode()和equals()方法
- 重写equals和hashcode方法
- HashCode和equals方法重写
- 重写equals() 和 hashCode()方法
- 重写hashCode()方法和重写equals()方法
- 站稳马步——(10)java I/O整理1
- 重写equals()方法和hashcode()方法
- 重写equals方法和hashcode方法
- 2010-10-23
- [Perl] Perl 特殊变量
- Oracle 11g R2 for Linux All in One 下载
- 深入分析 Linux 内核链表(转)
- POJ1050二维数组的最大子数组和
- 站稳马步——(1)重写 equals 和 hashCode 方法
- Facebook Zuckerberg: the game will never be developed_from Chinahourly.com
- Oracle数据库10个小问题(摘自叶正盛)
- jquery event trigger 分析
- 回流解析
- 生活的每一天
- 典型开源3D引擎分类比较[转]
- 乱弹:论编程的本质
- 如何单击dataGridView内容 获取该行数据