Java7 实现 equals、hashCode 和 compareTo 方法

Java7 引入了一些新的方法,可以更方便地处理 equals() 和 hashCode() 方法中的 null 值,以及 compareTo() 方法中的数字比较。

安全的 Null 值相等测试

假设你需要为下面的实体类 Person 实现 equals 方法:

public class Person {
    private String first;
    private String last;
    //....
}

首先,你不得不进行参数非 null 判断、参数类型判断,最后将参数转换为一个 Person 对象:

@Override
public boolean equals(Object o) {
    // 判断两者是否指向同一个对象
    if (this == o) {
        return true;
    }

    // 参数非空判断,类型判断
    if (o == null || getClass() != o.getClass()) {
        return false;
    }

    // 参数转换为 Person 类型
    Person person = (Person) o;
    if (!first.equals(person.first)) {
        return false;
    }
    return last.equals(person.last);
}

不幸的是,在 Java7 之前,每个类的 equals() 方法中都要这么做一遍(做了很多重复的工作)。在 Java7 之后,你不用再担心 Person 对象的 first 或者 last 字段可能为空,而只需调用:

@Override
public boolean equals(Object o) {
    if (this == o) {
        return true;
    }
    if (o == null || getClass() != o.getClass()) {
        return false;
    }

    Person person = (Person) o;
    return Objects.equals(first, person.getFirst()) && Objects.equals(last, person.getLast());
}

对于 Objects.equals(a,b),如果 a 和 b 都是 null,则返回 true;如果只有 a 为 null,则返回 false;其他情况则返回 a.equals(b)。

注意:一般来说,你应该把以前调用 a.equals(b) 的地方改为调用 Objects.equals(a,b)。

计算哈希码

我们还以上面的 Person 类为例,来计算它的哈希码。下面时通过 IDEA 自动帮我们生成的:

@Override
public int hashCode() {
    int result = first.hashCode();
    result = 31 * result + last.hashCode();
    return result;
}

如果 first 和 last 为 null,上面调用将会抛出 NullPointerException 异常。此时,我们可以借助 JDK7 提供的 Objects 类的 hashCode() 静态方法。对于 null 对象,Objects.hashCode() 方法会返回 0,因此你可以像下面这样来实现 hashCode() 方法:

@Override
public int hashCode() {
    return 31 * Objects.hashCode(first) + Objects.hashCode(last);
}

但是,我们还有更好的选择。Java7 中引入的可变参数方法 Objects.hash() 允许你指定任意个对象,并且它们的哈希码会被自动组合起来:

@Override
public int hashCode() {
    return Objects.hash(first, last);
}

⚠️注意:

(1)Objects.hash() 只是会调用从 Java5 开始就存在的 Arrays.hash() 方法。但是 Arrays.hash() 方法不是一个支持可变参数的方法,因此在使用上也不是很方便。

(2)对于 toString() 方法,你应该总是调用 String.valueOf(obj),因为它可以安全地处理 null 对象。如果 obj 为 null,那么会返回字符串 “null”。如果不喜欢这种方式,也可以调用 Objects.toString() 方法,并提供一个用于 null 对象的值。例如,Objects.toString(obj, "")。

比较数值类型对象

当你通过比较器来比较整型值时,因为允许返回任意负值或正值,所以它会试图返回二者之间相差的大小,但是实际上只需要知道符号就足够了。例如,假设你实现了一个 Point 类:

public int compareTo(Point other) {
    int diff = x - other.x; // 有溢出的风险
    if(diff != 0) return diff;
    return y - other.y;
}

但是这是有问题的。如果 x 非常大并且 other.x 是一个负数,那么二者之间的差值可能会导致溢出,如:Integer.MAX_VALUE - (-100) 结果为 -2147483549。这也使得实现 compareTo 方法变得很复杂。

在 Java7 后,你可以使用静态方法 Integer.compare() 来实现:

public int compareTo(Point other)(
    int diff = Integer.compare(x, other.x); // 没有溢出的风险
    if(diff != 0) return diff;
    return Integer.compare(y,other.y);
}

过去,某些开发人员会使用 new Integer(x).compareTo(other.x) 的方式,但是这会创建两个会自动装箱/拆箱的整型对象。相比之下,静态方法 compare() 使用的是 int 参数。

此外,Long、Short、Byte 和 Boolean 也都提供了各自的静态方法 compare()。如果你需要比较两个字符型值(char),可以直接将它们相减,因为这不会导致结果溢出(当然,这对于 short 和 byte 类型值也是一样)。

⚠️注意:Double 和 Float 类中的静态方法 compare() 从 Java 1.2 开始就存在了。

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号