用HashSet去掉List中重复对象的时候要注意
1. http://topic.csdn.net/t/20061128/17/5191858.html
如果List中的对象是自己定义的,则我们应该在改写了equals()方法的类必须同时改写hashCode()方法,并且保证两个equal的对象它们的hashCode()方法的返回值也是相同 的。否则的话这个类的对象放到 Hashtable, HashMap, HashSet等需要依赖hash值的容器类中的时候会出问题。
2.http://zhangxinzhou.blog.ccidnet.com/blog-htm-itemid-1251995-uid-36421-do-showone-type-blog.html
改写equals时总是要改写hashCode
java.lnag.Object中对hashCode的约定:
在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。
如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。
如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。
看个不改写hashCode导致使用hashMap不能出现预期结果的例子:
public final class PhoneNumber{
private final short areaCode;
private final short exchange;
private final short extension;
public PhoneNumber(int areaCode,int exchage,int extension){
rangeCheck(areaCode,999,"area code");
rangeCheck(exchange,999,"exchange");
rangeCheck(extension,9999,"extension");
this.areaCode=(short) areaCode;
this.exchange=(short) exchange;
this.extension=(short)extension;
}
private static void rangeCheck(int arg,int max, String name){
if(arg<0 || arg>max) throw new IllegalArgumentException(name+":"+arg);
}
public boolean equals(Object o){
if (o == this) reutrn true;
if (!(o instanceof PhoneNumber)) return false;
PhoneNumber pn=(PhoneNumber)o;
return pn.extension==extension && pn.exchange=exchange && pn.areaCode=areaCode;
}
//No hashCode method
...
}
现在有以下几行程序:
Map m=new HashMap();
m.put(new PhoneNumber(1,2,3),"Jenny");
则m.get(new PhoneNumber(1,2,3))的返回值什么?
虽然这个实例据equals是相等的,但由于没改写hashCode而致两个实例的散列码并不同(即违反第二条要求),因则返回的结果是null而不是"Jenny".
理想情况下,一个散列函数应该把一个集合中不相等的实例均匀地分布到所有可能的散列值上,下面是接近理想的“处方”:
把某个非零常数值(如17)保存在一个叫result的int类型的变量中;
对于对象中每个关键字域f(指equals方法中考虑的每一个域),完成以下步骤:
为该域计算int类型的散列码c:
如果该域是bloolean类型,则计算(f?0:1)
如果该域是byte,char,short或int类型,则计算(int)f
如果该域是long类型,则计算(int)(f^(>>>32))
如果该域是float类型,则计算Float.floatToIntBits(f)
如果该域是double类型,则计算Double.doubleToLongBits(f)得一long类型值,然后按前述计算此long类型的散列值
如果该域是一个对象引用,则利用此对象的hashCode,如果域的值为null,则返回0
如果该域是一个数组,则对每一个数组元素当作单独的域来处理,然后安下一步的方案来进行合成
利用下面的公式将散列码c 组合到result中。result=37*result+c;
检查“相等的实例是否具有相等的散列码?”,如果为否,则修正错误。
依照这个处方,得PhoneNumber的hashCode方法:
public int hashCode(){
int result=17;
result=37*result+areaCode;
result=37*result+exchange;
result=37*result+extension;
return result;
}
如果计算散列码的代价比较高,可以考虑用内部保存这个码,在创建是生成或迟缓初始化生成它。不要试图从散列码计算中排除掉一个对象的关键部分以提高性能。
3.http://www.blogjava.net/wang9354/archive/2009/02/25/256681.html
以下内容总结自《Effective Java》。
1.何时需要重写equals()
当一个类有自己特有的“逻辑相等”概念(不同于对象身份的概念)。
2.设计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方法。
3.当改写equals()的时候,总是要改写hashCode()
根据一个类的equals方法(改写后),两个截然不同的实例有可能在逻辑上是相等的,但是,根据Object.hashCode方法,它们仅仅是两个对象。因此,违反了“相等的对象必须具有相等的散列码”。
4.设计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。
5.示例
下面的这个类遵循上面的设计原则,重写了类的equals()和hashCode()。