对于所有的对象都通用的方法

覆盖 equals 请遵守通用约定

什么时候应该覆盖 equals 方法?
如果类具有自己特有的“逻辑相等”的概念,比如 Integer 或者 String。程序员需要利用 equals 方法来比较逻辑相等,而不是它们是否指向同一个对象。
有一种“值类”不需要覆盖 equals 方法
用实例受控确保“每个值最多只存在一个对象的类”,比如 Boolean ,Enum 等类。
在覆盖 equals 方法的时候,必须遵守它的通用规定

  • 自反性:对于任何非 null 的 x,x.equals(x)必须返回 true。
  • 对称性:y.equals(x) = x.equals(y)
  • 传递性:x.equals(y) = true = y.equals(z),则 y.equals(z) = true
  • 一致性:当对象没有被修改,多次调用 x.equals(y) ,返回的结果相同。
  • x.equals(null) 必须返回 false。
  • 非空性:所有非空对象都不能等于 null。
    如何实现高质量的 equals 方法
  • 使用 == 操作符检查“参数是否为这个对象的引用”。
  • 使用 Instanceof 操作符检查“参数是否为正确的类型”。此处的类型可以为接口,允许该接口的实现类间进行比较。
  • 把参数转换为正确的类型。
  • 检查“关键”域是否匹配。
    对于非 float 和 double 类型的基本类型域,使用 == 比较。否则使用 Float.compare 方法和 Double.compare 方法。
    举例
@Override
public boolean equals(Object o) {
	if (o == this) 
		return true;
	if (!(o instanceof PhoneNumber)) 
		return false;
	PhoneNumber pn = (PhoneNumber)o;
	return pn.lineNum == lineNum && pn.prefix == prefix
						&& pn.areaCode == areaCode;
}

警告

  • 覆盖 equals 时总要覆盖 hashCode。
  • 不要将 equals 声明中的 Object 对象替换为其他的类型。

覆盖 equals 时总要覆盖hashCode

如果不这样做的话,该类就无法结合所有基于散列的集合一起正常运作,如 HashMap 和 HashSet。
规定:相等的对象必须具有相等的散列码。
HashMap 类的 get 方法是根据参数的散列码获取的。
举例:一个合法但不好用的 hashCode 方法永远都不应该被使用。

@Override
public int hashCode() {
	return 42;
}

举例:用关键域的参数来计算散列码,并用素数相乘,31可以用位移和减法代替乘法。

@Override
public int hashCode() {
	int result = Short.hashCode(areaCode);
	result = 31 * result + Short.hashCode(prefix);
	result = 31 * result + Short.hashCode(lineNum);
	return result;
}

始终要覆盖 toString

提供好的 toString 实现可以使类用起来更加舒适,使用了这个类的系统也更易于调试。
在静态工具类和枚举类中编写 toString 方法是没有意义的。

谨慎地覆盖 clone

约定:

  • x.clone() != x
  • x.clone().getClass() == x.getClass()
  • x.clone().equals(x)
    不可变的类永远都不应该提供 clone 方法。
    举例:
@Override
public PhoneNumber clone() {
	try{
		return (PhoneNumber) super.clone;
	} catch (CloneNotSupportedException e) {
		throw new AssertionError();
	}
}

实际上,clone 方法就是另一个构造器;必须确保它不会伤害到原始对象,并确保正确地创建被克隆对象中的约束条件。
举例:为了将 elements 数组中的内容全部克隆,最容易的做法是,递归调用。

@Override
public Stack clone() {
	try{
		Stack result = (Stack) super.clone();
		result.elements = elements.clone();
		return result;
	} catch (CloneNotSupportedException e) {
		throw new AssertionError();
	}
}

如果你编写线程安全的类准备实现Cloneable 接口,要记住它的 clone 方法必须得到严格的同步。
除此之外,还有一些好的办法。

  • 提供一个拷贝构造器。
public Yum(Yum yum) {...}
  • 拷贝工厂方法
public static Yum newInstance(Yum yum) {...}

这种方法的好处是你甚至可以转换类型。

新的接口不应该扩展 Cloneable 接口,新的可扩展的类也不应该实现这个接口。

考虑实现 Comparable 接口

为实现 Comparable 接口的对象数组进行排序非常简单。

Arrays.sort(a);

对存储在集合中的 Comparable 对象进行搜索、计算极限值以及自动维护也同样简单。

public class wordList {
	public static void main(String[] args) {
		Set<String> s = new TreeSet<>();
		Collections.addAll(s,args);
		System.out.println(s);
	}
}

如果你正在编写一个值类,它具有非常明显的内在排序关系,比如按字母顺序、按数值顺序或者按年代顺序,那你就应该坚决考虑实现 Comparable 接口。

public interface Comparable<T> {
    /**
     * Compares this object with the specified object for order.  Returns a
     * negative integer, zero, or a positive integer as this object is less
     * than, equal to, or greater than the specified object.
     *
     * <p>The implementor must ensure <tt>sgn(x.compareTo(y)) ==
     * -sgn(y.compareTo(x))</tt> for all <tt>x</tt> and <tt>y</tt>.  (This
     * implies that <tt>x.compareTo(y)</tt> must throw an exception iff
     * <tt>y.compareTo(x)</tt> throws an exception.)
     *
     * <p>The implementor must also ensure that the relation is transitive:
     * <tt>(x.compareTo(y)>0 && y.compareTo(z)>0)</tt> implies
     * <tt>x.compareTo(z)>0</tt>.
     *
     * <p>Finally, the implementor must ensure that <tt>x.compareTo(y)==0</tt>
     * implies that <tt>sgn(x.compareTo(z)) == sgn(y.compareTo(z))</tt>, for
     * all <tt>z</tt>.
     *
     * <p>It is strongly recommended, but <i>not</i> strictly required that
     * <tt>(x.compareTo(y)==0) == (x.equals(y))</tt>.  Generally speaking, any
     * class that implements the <tt>Comparable</tt> interface and violates
     * this condition should clearly indicate this fact.  The recommended
     * language is "Note: this class has a natural ordering that is
     * inconsistent with equals."
     *
     * <p>In the foregoing description, the notation
     * <tt>sgn(</tt><i>expression</i><tt>)</tt> designates the mathematical
     * <i>signum</i> function, which is defined to return one of <tt>-1</tt>,
     * <tt>0</tt>, or <tt>1</tt> according to whether the value of
     * <i>expression</i> is negative, zero or positive.
     *
     * @param   o the object to be compared.
     * @return  a negative integer, zero, or a positive integer as this object
     *          is less than, equal to, or greater than the specified object.
     *
     * @throws NullPointerException if the specified object is null
     * @throws ClassCastException if the specified object's type prevents it
     *         from being compared to this object.
     */
    public int compareTo(T o);
}

约定:当这个对象与指定对象比较的时候,当该对象小于、等于或大于指定对象时,分别返回一个负整数,零或者正整数。

  • sgn(x.compareTo(y))==-sgn(y.compareTo(x)) (暗示着抛异常也如此)
  • 传递性
  • 强烈建议(x.compareTo(y) == 0) == (x.equals(y))

视图模式:

下面是 HashMap 这个类的源码

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    
    //父类 AbstractMap 中创建了 keySet 实例,如下
	// transient Set<K> keySet;
    
    // 外部的类通过调用该方法获得 keySet 实例,但没办法对底层的 keySet 进行操作
     public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            ks = new KeySet();
            keySet = ks;
        }
        return ks;
    }
    
//  内部类 KeySet ,进行权限控制
    final class KeySet extends AbstractSet<K> {
        public final int size()                 { return size; }
        public final void clear()               { HashMap.this.clear(); }
        public final Iterator<K> iterator()     { return new KeyIterator(); }
        public final boolean contains(Object o) { return containsKey(o); }
        public final boolean remove(Object key) {
            return removeNode(hash(key), key, null, false, true) != null;
        }
        public final Spliterator<K> spliterator() {
            return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
        }
        public final void forEach(Consumer<? super K> action) {
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
                int mc = modCount;
                for (int i = 0; i < tab.length; ++i) {
                    for (Node<K,V> e = tab[i]; e != null; e = e.next)
                        action.accept(e.key);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
    }
    
}

言归正传,如果你想为一个实现了 Comparable 的类增加值组件,请不要扩展这个类;而是要编写一个不相关的类,其中包含第一个类的一个实例,然后提供一个“视图”方法返回这个实例。这样既可以让你自由地在第二个类上实现 compareTo 方法,同时也允许它的客户端在必要的时候,把第二个类的实例视同第一个类的实例。

对于(x.compareTo(y) == 0) == (x.equals(y)) 这个约定,BigDecimal 中的这两个方法是不同的。

Java 7 中已经在所有的装箱基本类型中增加了静态的 compare 方法。

举例:先比较最关键的域

public int CompareTo(PhoneNumber pn) {
// Short 类是 Comparable 的实现类
	int result = Short.compare(areaCode,pn.areaCode);
	if (result == 0) {
		result = Short.compare(prefix,pn.prefix);
		if (result == 0) {
			result = Short.compare(lineNum,pn.lineNum);
		}
	}
	return result;
}

在 Java 8 中,Comparator 接口配置了一组比较器的构造方法,使得比较器的构造工作变得非常流畅。

private static final Compartor<PhoneNumber> COMPARATOR = 
		comringInt((PhoneNumber pn) -> pn.areaCode)
		  .thenComparingInt(pn -> pn.prefix)
		  .thenComparingInt(pn -> pn.lineNum);


public int compareTo(PhoneNumber pn) {
	return COMPARATOR.compare(this,pn);
}

使用 compare 方法时,不要两个数相减,很容易造成整数溢出。

可以使用如下静态方法

static Comparator<Object> hashCodeOrder = new Comparator<>() {
	public int compare(Object o1,Object o2) {
		return Integer.compare(o1.hashCode(),o2.hashCode());
	}
}

或是比较器构造方法

static Comparator<Object> hashCodeOrder = 
		Comparator.comaringInt(o -> o.hashCode());

总结:避免使用 < 和 > 操作符,而应该使用装箱基本类型中的 compare 方法,或者在 Comparator 接口中使用比较器构造方法。