对象的通用方法
笔记:
common method for every thing:
1、覆盖equals时请遵守通用约定
不覆盖equals就可以避免覆盖equals带来的错误,满足一下任意条件即可:
类的每个实例本质上都是唯一的,对于代表实体而不是值的类来说确实如此
不必要关心类是否提供了“逻辑相等”的测试功能
超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的
类是私有的或者是包级私有的,可以确定它的equals方法永远不会被调用
当类具有自己特有的“逻辑相等”概念,而且超类还没有覆盖equals以实现期望的行为,这时我们就需要覆盖equals的方法。有一种特殊的值类不需要覆盖equals方法,即实例受控确保每个值至多存在一个对象的类。
覆盖equals方法需要遵守它的通用约定:
自反性:对于任何非null的引用值x, x.equals(x)必须返回true
对称性:对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true
传递性:对于任何非null的引用值x和y和z,如果x.equals(y)返回true,并且y.equals(z)返回true, x.equals(z)也必须返回true
一致性:对于任何非null的引用值x和y,只要equals的比较操作在对象中的所用信息没有被修改,多次调用x.equals(y)会返回一致的值
对于任何非null的引用值x,x.equals(null)必须返回false
虽然没有一种令人满意的方式可以既拓展不可实例化类又增加值组件,但是还是有一种不错的权宜之计:复合优先于继承。
保证一致性要求equals方法不能依赖于不可靠的资源
非空型检查可在equals第一行加null检测,但可以不必要,instanceof操作符检测是否正确类型的时候如果是null一定返回false
高质量equals方法的诀窍:
使用==操作符检查“参数是否为这个对象的引用”
使用instanceof操作符检查“参数是否为正确的类型”
把参数转换成正确的类型
对于该类中每个关键域,检查参数中的域是否与该对象中对应的域相匹配
注意:
对于既不是float也不是double类型的基本类型域,可以使用==操作符进行比较,对于对象引用域,可以递归调用equals方法;对于float域和double域,使用包装类的compare方法比较。为什么要对于float和double进行特殊处理呢?因为存在Float.NaN、-0.0f类似的double常量。
域的比较顺序可能会影响到equals方法的性能。为了获得最佳性能,有限比较最有可能不一致的域,或者是开销最低的域
2、覆盖equals时总要覆盖hashCode
在每个覆盖了equals方法的类中,也必须覆盖hashCode方法。如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作,例如HashMap、HashSet等。
约定内容,JAVASE6:
1、在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。
2、如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果
3、如果两个对象根据equals(Object)方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生不同的整数结果
好的散列函数通常倾向于“为不相等的对象产生不相等的散列码”。一种简单的解决方式:
1、把某个非0的常数值,比如17,保存在一个名为result的int类型的变量中
2、对于对象中每个关键域f(指equals方法中涉及的每个域)完成以下步骤:
a、为该域计算int类型的散列码c:
1、如果该域是boolean类型,则计算(f?1:0)
2、如果该域是byte、char、short或者int类型,则计算(int)f
3、如果该域是float类型,则计算Float.floatToIntBits(f)
4、如果该域是double类型,则计算Double.doubleToLongBits(f),然后按照long再处理一下
5、如果该域是一个对象引用,递归为这个域调用hashCode。如果需要更复杂的比较,则为这个域计算一个“范式”,然后针对这个范式调用hashCode。如果这个域的值为null,则返回0
6、如果该域是一个数组,则需要把每一个元素当成单独的域来处理,如果数组域中每一个元素都很重要,利用Arrays.hashCode方法
b、按照下面的公司,将2.a中计算得到的散列码c合并到result中:
result = 31 * result + c;
3、返回result
注意:计算散列码的时候,可以把冗余域排除在外,换句话来说,如果一个域中的值可以根据其他参与计算的其他值计算出来,则可以把这样的域排除;必须排除equals比较计算中没有用到的任何域
上面的17是任选的。选择31是因为它是一个奇素数,且31有一个很好的特性,即用移位和减法代替乘法可以得到更好的性能:31*i == (i<<5) - i;
不要试图从散列码计算中排除掉一个对象的关键部分来提高性能
3、始终要覆盖toString
Object提供的toString的实现:类名称 + @ + 散列码的无符号十六进制表示法
toString约定进一步指出:建议所有的子类都覆盖这个方法,toString中应该打注释表明格式
4、谨慎地覆盖clone
Cloneable接口的目的是作为对象的一个mixin接口,表明这样的对话允许克隆。但是这个接口缺少一个clone方法。Object 的clone方法是受保护的(native方法)。
既然Cloneable接口并没有包含任何方法,那么它到底有什么作用呢?它决定了Object中受保护的clone方法实现的行为:如果一个类实现了Cloneable,Object的clone方法就返回该对象的逐域拷贝,否则就会抛出CloneNotSupportedException。
Clone方法的通用约定是非常弱的:
JAVASE6中:
x.clone() != x 得到true
x.clone().getClass() == x.getClass() 得到true
但这些都不是绝对的要求
拷贝对象往往会导致创建它的一个类实例,但它同时也会要求拷贝内部的数据结构。这个过程中没有调用构造器
在实践过程中,我们期望拓展了一个类,并且从子类中调用了super.clone,返回的对象将是子类的实例。如果所有的超类都遵守这个规则,那么调用super.clone最终会调用Object.clone方法
从每个超类的角度看,这个对象将是原始对象功能完整的克隆。在这个类中声明的域都将等同于被克隆对象中相对应的域。如果每个域包含一个基本类型的值,或者包含一个指向不可变对象的引用,那么它们返回的对象则可能是你所需要的对象。如果对象中包含的域引用了可变的对象,使用上述简单的clone实现可能导致灾难性的后果。解决方式是将可变对象也进行一份拷贝。
clone有点恶心,可以考虑用拷贝构造器和拷贝工厂来替代它
5、考虑实现Comparable接口
Comparable接口中唯一的方法。compareTo方法允许进行同等性比较,而且允许执行顺序比较,除此之外与Object的equals具有相似的特征,并且还是个泛型。一旦实现了Comparable接口,就可以跟许多泛型算法以及依赖于该接口的集合实现进行协作。事实上,Java平台库中所有的值类都实现了Comparable接口
下面说明中,符号sgn表示数学中的signum函数,根据表达式的值为负值、零和正值,分别返回-1、0或1
x和y必须满足sgn(x.compareTo(y) == - sgn(y.compareTo(x)))
比较关系是可传递的,(x.compareTo(y) > 0 && y.compareTo(z) > 0)暗示着x.compareTo(z) > 0
确保x.compareTo(y)==0暗示所有的的z都满足sgn(x.compareTo(z)) == sgn(y.compareTo(z))
好像违反了hashCode约定会破坏其他依赖于散列做法的类一样,违反compareTo约定的类也会破坏其他依赖于比较关系的类,如:TreeSet和TreeMap等
比较整数型基本类型的域,可以使用关系操作符<和>,浮点域用Double或Float的compare,对于数组域,则要把这些指导原则应用到每个元素上。比较的顺序也是从关键逐步到所有的重要域
有关代码:
package chapter2; /** * @author zhen * @Date 2018/9/27 17:11 */ public class CaseInsensitiveString { private final String s; public CaseInsensitiveString(String s) { if (s == null) { throw new NullPointerException(); } this.s = s; } //对称性不一致 public boolean equals(Object o) { if (o instanceof CaseInsensitiveString) { return s.equalsIgnoreCase(((CaseInsensitiveString) o).s); } if (o instanceof String) { return s.equalsIgnoreCase((String)o); } return false; } public static void main(String[] args) { System.out.println(Float.NaN == -0.0F); System.out.println(Float.NaN == Float.NaN); System.out.println(Float.compare(Float.NaN, Float.NaN)); } }
package chapter2; import java.awt.*; /** * @author zhen * @Date 2018/9/27 17:22 */ public class ColorPoint extends Point { private final Color color; public ColorPoint(int x, int y, Color color) { super(x, y); this.color = color; } //equals默认继承的是父类的,传递性不保持 //这样对称性又不满足 // @Override public boolean equals(Object o) { // if (!(o instanceof ColorPoint)){ // return false; // } // return super.equals(o) && ((ColorPoint) o).color == color; // } //提供了对称性却牺牲了传递性 // @Override public boolean equals(Object o) { // if (!(o instanceof Point)) { // return false; // } // // if (!(o instanceof ColorPoint)){ // return o.equals(this); // } // // return super.equals(o) && ((ColorPoint) o).color == color; // } }
package chapter2; import java.awt.*; /** * @author zhen * @Date 2018/9/28 9:27 */ public class ColorPoint1 { private final Point point; private final Color color; public ColorPoint1(int x, int y, Color color) { if (color == null) { throw new NullPointerException(); } point = new Point(x, y); this.color = color; } public Point asPoint() { return point; } @Override public boolean equals(Object o) { if (!(o instanceof ColorPoint1)) { return false; } ColorPoint1 cp = (ColorPoint1) o; return cp.point.equals(point) && cp.color.equals(color); } }
package chapter2; /** * @author zhen * @Date 2018/9/28 13:49 */ 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 next) { this.key = key; this.value = value; this.next = next; } //这样如果列表过长可能栈内存溢出,考虑用迭代替代递归 // Entry deepCopy() { // return new Entry(key, value, next == null ? null : next.deepCopy()); // } Entry deepCopy() { Entry result = new Entry(key, value, next); for(Entry p = result; p.next != null ; p = p.next){ p.next = new Entry(p.next.key, p.next.value, p.next.next); } return result; } } @Override public HashTable clone() { try{ HashTable result = (HashTable) super.clone(); result.buckets = new Entry[buckets.length]; for(int i = 0; i < buckets.length; i++){ if (buckets[i] != null) { result.buckets[i] = buckets[i].deepCopy(); } } return result; }catch (CloneNotSupportedException e) { throw new AssertionError(); } } }
package chapter2; import java.util.HashMap; import java.util.Map; /** * @author zhen * @Date 2018/9/28 10:34 */ public class PhoneNumber implements Cloneable { private final short areaCode; private final short prefix; private final short lineNumber; public PhoneNumber(int areaCode, int prefix, int lineNumber) { rangeCheck(areaCode, 999, "area code"); rangeCheck(prefix, 999, "prefix"); rangeCheck(lineNumber, 9999, "line number"); this.areaCode = (short) areaCode; this.prefix = (short) prefix; this.lineNumber = (short) lineNumber; } private static void rangeCheck(int arg, int max, String name) { if (arg < 0 || arg > max) { throw new IllegalArgumentException(name + ":" + arg); } } @Override public boolean equals(Object o) { if ( o == this) { return true; } if (!(o instanceof PhoneNumber)) { return false; } PhoneNumber pn = (PhoneNumber) o; return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode; } @Override public int hashCode() { int result = 17; result = 31 * result + lineNumber; result = 31 * result + prefix; result = 31 * result + areaCode; return result; } @Override public PhoneNumber clone() { try{ return (PhoneNumber) super.clone(); }catch (CloneNotSupportedException e) { throw new AssertionError(); } } public static void main(String[] args) { Map<PhoneNumber , String> m = new HashMap<>(); m.put(new PhoneNumber(707, 867, 5309), "Jenny"); System.out.println(m.get(new PhoneNumber(707, 867, 5309))); } }
package chapter2; /** * @author zhen * @Date 2018/9/27 17:17 */ public class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object o){ if (!(o instanceof Point)){ return false; } Point p = (Point) o; return p.x == x && p.y == y; } // @Override public boolean equals(Object o) { // if (o == null || o.getClass() != getClass()){ // return false; // } // Point p = (Point) o; // return (p.x == x) && (p.y == y); // } }

浙公网安备 33010602011771号