尽管Object是一个具体类,但是设计Object类主要是为了扩展。它的所有非final方法(equals、hashCode、toString、clone和finalize)都有明确的通用约定,因为它们就是被设计成要被覆盖的。
但是如果类具有自己特有的“逻辑相等”概念(不等同于对象相等的概念),而且超类还咩有覆盖equals以实现期望的行为,这时我们就要覆盖equals方法。这通常属于“值类”的情形。
有一种“值类”不需要覆盖equals方法,即用实例受控确保“每个值至多只存在一个对象”(即单例类)的类、枚举类型就属于这种类。对于这种类,逻辑相等与对象相等是同一回事。
一个很常见的错误根源在于没有覆盖hashCode方法。
考虑下面的类:
public final class PhoneNumber {
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,"lineNumber");
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;
//未重写hashCode方法
...
}
}
这时,若考虑:
Map m = new HashMap<>();
m.put(new PhoneNumber(123,456,789),"Jenny");
如果期望调用:
m.get(new PhoneNumber(123,456,789));
返回的是“Jenny”的话,实际上无法做到,因为它返回的是null,因为这里有两个PhoneNumber实例,第一个被插入到Map的散列桶中,第二个用于获取该对象,但两个对象的散列码不同,因为hashCode默认返回的是对象的地址值,get方法会首先判断Map中是否有与目标对象的hashCode相同的对象,显然,这是两个对象,hashCode明显不同,于是返回的结果为false,也就找不到了。所以需要重写hashCode方法,好的重写方式是为不相等的对象产生不相等的散列码,为相等的对象产生相等的散列码,即如果两个对象equals为true,那么两个对象的hashCode必相等,如果两个对象equals为false,那么两个对象的hashCode不相等。
为上述PhoneNumber类覆盖hashCode方法:
@Override
public int hashCode() {
int result = 17;
result = 31 * result + areaCode;
result = 31 * result + prefix;
result = 31 * result + lineNumber;
return result;
}
还可将缓存起来:
private volatile int hashCode;
...
@Override
public int hashCode() {
int result = hashCode;
if(result == 0) {
result = 17;
result = 31 * result + areaCode;
result = 31 * result + prefix;
result = 31 * result + lineNumber;
}
return result;
}
java.lang.Object提供了toString方法的一个实现,它返回是:类的名称,以及一个“@”符号,接着是散列码的无符号十六进制表示法,例如"PhoneNumber@163b91"。
在实际应用中,toString方法应该返回对象中包含的所有值得关注的信息。
要为toString返回值中包含的所有信息,提供一种编程式的访问途径(如getter方法)。
如需要克隆对象,需要实现Cloneable接口。
有关clone方法的详解,可以参考这篇文章:
详解Java中的clone方法 – 原型模式
compareTo方法并没有在Object中声明。它是Comparable接口中唯一的方法。compareTo方法不单允许进行简单的等同性比较,而且允许顺序比较。除此之外,它与Object的equals方法具有相似的特征,它还是个泛型。类实现了Comparable接口,就表明它的实例具有内在的排序关系。为实现Comparable接口的对象数组进行排序非常简单:Arrays.sort(a);。对存储在集合中的Comparable对象进行搜索、计算极限值以及自动维护也同样简单。例如,下面的代码依赖于String实现了Comparable接口,它去除命令行参数列表中的重复参数,并按字母顺序打印出来:
package Effective_Java;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
/**
* @author [email protected]
* @date 18-5-27 下午12:11
*/
public class Demo1 {
public static void main(String[] args) {
Set set = new TreeSet<>();
Collections.addAll(set, args);
System.out.println(set);
}
}
一旦类实现了Comparable接口,它就可以跟许多泛型算法以及依赖于该接口的集合实现进行协作。付出很小的努力就可以获得非常强大的功能。
compareTo方法的通用约定与equals方法的相似:将这个对象与指定的对象进行比较。当该对象小雨、等于或大于指定对象的时候,分别返回一个负整数、零或者正整数。如果由于指定的对象的类型而无法与该对象进行比较,则抛出ClassCastException异常。
package Effective_Java;
/**
* @author [email protected]
* @date 18-5-27 下午12:11
*/
public class Demo1 implements Comparable {
public int a, b, c;
public Demo1(int a, int b, int c) {
this.a = a;
this.b = b;
this.c = c;
}
/**
* 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.
*
* The implementor must ensure sgn(x.compareTo(y)) ==
* -sgn(y.compareTo(x)) for all x and y. (This
* implies that x.compareTo(y) must throw an exception iff
* y.compareTo(x) throws an exception.)
*
*
The implementor must also ensure that the relation is transitive:
* (x.compareTo(y)>0 && y.compareTo(z)>0) implies
* x.compareTo(z)>0.
*
*
Finally, the implementor must ensure that x.compareTo(y)==0
* implies that sgn(x.compareTo(z)) == sgn(y.compareTo(z)), for
* all z.
*
*
It is strongly recommended, but not strictly required that
* (x.compareTo(y)==0) == (x.equals(y)). Generally speaking, any
* class that implements the Comparable 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."
*
*
In the foregoing description, the notation
* sgn(expression) designates the mathematical
* signum function, which is defined to return one of -1,
* 0, or 1 according to whether the value of
* expression 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.
*/
@Override
public int compareTo(Demo1 o) {
int result = a - o.a;
if(result != 0) {
return result;
}
result = b- o.b;
if(result != 0) {
return result;
}
return c - o.c;
}
}
总结:本章主要讲解了在面向对象(对象在哪!)编程时,需要对Object类中的一些方法进行覆盖时所要注意的一些点,以及在涉及到比较操作时,实现Comparable接口所需要注意的一些事情。共勉!