equals所期望的结果
类的每个实例本质上都是唯一的。不关心类是否提供了“逻辑相等”的测试功能。超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的。类时私有的或者包私有的,可以确定它的equals方法永远不会调用。需要覆盖的时机:
父类没有实现所期望的以上的equals实现
“最多只存在一个对象”的类不需要覆盖equals,例如枚举类型
equals的等价关系
自反性。任何非null引用值,x.equals(x)返回true。
Set集合中重复添加同意一个引用值会怎样?
对称性。对于任何非null的x和y,x.equals(y) 返回true,那么y.equals(x) 也返回true。
自定义类A实现了不区分大小写的比较,new A(“Aa”).equals(“aa”)返回true,但是“aa”.equals(new A(“Aa”))的返回值却由String类中的equals方法决定。
传递性。对于任何非null的x,y,z,如果x.equals(y) 为true,且 y.equals(z) 也为true,那么x.equals(z) 也为true。
存在扩展可实例化与增加主键值的时候,容易违反传递性。考虑使用抽象类并在其子类中添加属性,例如Shape。
一致性。对于任何非null引用值x,y,只要x.equals(y)返回true,那么在引用对象信息没有被修改,那么每次返回的仍是一致的true。
要保证一致性,不要使equals依赖于不可靠的的资源。例如java..net.URL的equals依赖于主机的IP,IP可能随着时间的推移而改变。
对于任何非null引用值x,x.equals(null)必须返回false。
高质量equals
使用==检查是待比较参数是否是同意对象引用本身使用instanceof检查参数是否是正确类型把参数转换成正确的类型,使用instanceof判断检查参数中域与该对象中的对应域相匹配。除float 、double外的基本类型使用==比较对象引用域可以递归调用equals对于float,double分别使用Float.compare(),Double.compare()数组判等使用Arrays.equals()。对于允许null的域,尽可能避免NullPointException,可以做判空操作对个域比较的时候,或者比较比较步骤较多的时候,比较顺序从最有可能不一致的开始注意点
覆盖equals的时候总要覆盖hashCode不要让equals过于智能。过度的寻求某种不必要的等价关系。不要将equals中参数转换成其他类型。某些情况能增加性能,但是比较复杂性会增加(不推荐)。 //本应该是: public void equals(Object o){ .... } //转换参数成其他类型: public void equals(MyClass o){ .... } //加上@Override导致变异不通过,因为父类的equals方法不存在此重写方法。 @Override public void equals(MyClass o){ .... }JavaSE6中的Object规范
程序执行期间,对象的equals方法所用到的比较信息没有被修改,对同一个对象的多次调用hashCode始终如一的返回同一个整数,但是多次执行的过程中,所返回的整数可以不一致(信息修改后)。两个对象的equals返回true,那么这两个对象的hashCode返回的整数必须相等。(所以覆盖equals总要覆盖hashCode)两个对象的equals返回false,那么不要求两个对象的hashCode返回不一样的整数结果,但是在使用hash的相关集合框架中,返回不一样的结果能提高性能HashMap,HashSet等hash集合框架,依赖元素的hashCode进行散列,因此当equals返回true的时候,hashCode返回的结果也一样,可以保证是一个相等的元素。在put ,get,remove等操作均会使用到元素的hashCode。因此hashcode能影响到HashMap等Hash结合框架的性能。
计算hashcode的一些方法:
boolean 计算(f?1:0)byte,char,short,int 计算(int)flong 计算(int)(f^(f>>>32))float 计算Float.floatToInBits(f);double 计算Double.doubleToLongBits(f),得到的结果再按long类型处理对象引用 null则返回0,否则递归调用hashCode数组 用以上规则计算每个元素的hashCode,或则使用Arrays.hashCode()最后返回将上述计算得到的结果c,(result = 31 * result +c),返回 result。
注意:散列码计算过程中排除掉冗余域,也就是没有参与到equals中的域
上述使用到了31进行最后结果的处理,因为31有一个很好的特性是可以使用移位和减法来代替乘法。31*i = (i<<5)-i 而且VM会自动完成这种优化。
此外散列码的计算可能是开销很大的,可以考虑懒加载,既是只在第一次调用hashCode的时候进行计算,然后结果保存在实例中,下次直接返回保存的结果。但是前提是类是不可变的。
Object默认的toString返回的结果是:类名@xxxxxx,其中xxxxxx是该对象的散列码的十六进制表示
覆盖toString方法可以在调试或者打印对象信息的时候更易于阅读理解。
但是在该类被广泛使用的时候要保证toString的返回格式的一致性,可利于维护。
在JavaSE6中的clone方法的通用约定:
x.clone()!=xtruex.clone().getClass() == x.getClass()true(不绝对要求如此)x.clone().equals(x)true(不绝对要求如此)
Object中的clone方法是protected的,而一个类实现了Cloneable接口改变了clone的行为,是的clone可以返回一个该对象得到逐域拷贝对象,使得不通过构造器就可以生成一个对象,否则抛出CloneNotSupportedExcetion。
如果覆盖了非final类的clone方法,则应该返回一个通过super.clone()得到的对象。而对于实现了Cloneable接口的类,如果所有超类否提供了良好的clone实现,那么我们可以在实现了Cloneable的子类实现一个共有的clone的方法,否则我们不应该提供任何clone实现。
当需要clone的类中存在引用类型且不是final的域的时候,我们仍需要调用该引用对象的clone方法进行clone。
class Wheel implements Cloneable{ int size; int count; @Override public Wheel clone(){ return (Wheel)super.clone(); } } class Car implements Cloneable{ Wheel wheel; String name; @Override public Car clone(){ try{ Car car = (Car)super.clone(); car.wheel = wheel.clone();//省略这一行,那么car.wheel == this.wheel(浅拷贝) return car; }catch(CloneNotSupportedException e){ throw new AssertionError(); } } }另一种情况,存在引用链的情况下,我们需要递归的进行复制
public class HashTable implements Cloneable{ private Entry[] buckets = .....; private static class Entry{ final Object key; Object value; Entry next; Entry(Object key,Object value,Entry e){ .... } public deepCopy(){ //递归调用可能会栈溢出 return new Entry(key,value,next==null?null:next.deepCopy()); //使用迭代 Entry e = new Entry(key,value,next); for(Entry p = e;p.next!=null;p=p.next) p.next = new Entry(key,value,p.next.next); return e; } } @Override public HashTable clone(){ try{ HashTable ht =(HashTable)super.clone(); //这样得到的buckets中的元素与this的buckets中的元素时指向同一个对象的(浅拷贝) //ht.buckets = buckets.clone(); //单独拷贝每个buckets中的链表元素(深拷贝) ht.buckets = new Entry[buckets.length]; for(int i=0,s=buckets.lenght;i<s;i++){ if(buckets[i]!=null){ ht.buckets[i] = buckets[i].deepCopy(); } } return ht; }catch(CloneNotSupportedException e){ throw new AssertionError(); } } }多线程环境霞需要自己实现同步的clone
其他实现对象拷贝的方法:
拷贝构造函器
public Car(Car car){ ..... }静态工厂拷贝方法
public static Car newInstance(Car car){ ...... }Java中的使用例子:
基于接口的拷贝:Collection, Map。HashSet hs = new HashSet(); TreeSet ts = new TreeSet(hs);
comparable接口属于一个泛型接口,一个类实现了该接口可以和许多依赖该接口的集合实现协作功能,很明显的一个功能就是内在的排序关系。
Comparable接口中待实现的的方法是compareTo(T t)。返回正整数,0,负整数来表示当前对象大于,等于,小于参数对象。
compareTo同样应该满足:自反性,传递性,对称性
compareTo返回0是,同样应该满足equals返回true
compareTo需要比较的值域较多是,从最右可能产生不一致的域开始比较,依次类推
对于float double类型的比较,使用Float.compare() ,Double.compare();
compareTo与equals的差别是compareTo是参数化的,不必进行参数转化,此外其他的很多equals中的特点同样适用与compareTo。
