学习笔记(二):Java中的equals()和hashCode()

引言

最近找实习工作,看了许多大神的Java面经,其中equals()和hashCode()的相关问题被多次提及。但是因为平常都是使用Lombok,直接一个@Data注解完事儿,所以对这一块的知识还需要加深了解。因此在这里整理一下,方便以后复习,也希望能帮到一些小伙伴。

一、equals()方法

1. equals()方法的作用

equals()方法是Java中Object类里自带的一个方法,具体描述和源码如下所示:

equals

public boolean equals(Object obj)

Indicates whether some other object is "equal to" this one.

The equals method implements an equivalence relation on non-null object references:

  • It is reflexive: for any non-null reference value xx.equals(x) should return true.
  • It is symmetric: for any non-null reference values x and yx.equals(y) should return true if and only if y.equals(x) returns true.
  • It is transitive: for any non-null reference values xy, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  • It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
  • For any non-null reference value xx.equals(null) should return false.

The equals method for class Object implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference values x and y, this method returns true if and only if x and y refer to the same object (x == y has the value true).

Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes.

Parameters:

obj - the reference object with which to compare.

Returns:

true if this object is the same as the obj argument; false otherwise.

—— Java 8 官方文档

 本人渣翻:

equals()用于判断其他一些对象与当前这个对象是否“相等”。

equals()方法在非空对象上必须遵守以下的原则:

  • 自反性:对于任何非空对象x,x.equals(x)的返回值应该为true。
  • 对称性:对于任何非空对象x和y,x.equals(y)的返回值为true,当且仅当y.equals(x)返回值为true时。
  • 传递性:对于任何非空对象x,y和z,如果x.equals(y)返回值为true且y.equals(z)返回值为true,则x.equals(z)的返回值应当为true。
  • 一致性:对于任何非空对象x和y,在不对任何对象参数进行修改的前提下,多次调用x.equals(y)的返回值应该一直为true或者是false。
  • 对于任何非空对象x,x.equals(null)的返回值应该为false。

 equals()方法实现了Object类在对象最具区分性的可能等价关系;也就是说,对于任何非空对象x和y,当且仅当x和y表示同一个对象时(x == y为true),该方法返回true。

注意,一般情况下,重载该方法时应该重载hashCode()方法来维护hashCode()方法的一般约定,因为该方法规定,相同的对象必须具有相同的哈希值。

形参:

obj - 需要比较的对象。

返回值:

如果当前对象与形参对象obj相同,返回true,否则返回false。

public boolean equals(Object obj) {
    return (this == obj);
}

—— JDK 8

 2. equals()方法的使用

因为equals()方法时Object类中的方法,因此,如果我们不对Object类中的equals()方法进行重载,那么系统会为我们默认调用Object类中的equals()方法,也即比较两个对象的地址(等价于“==”)。而在平常的业务和使用中,Object类中的equals()方法并不能满足我们的需要(我为什么不直接用“==”比较呢),因此我们需要重写equals()方法。

按照上面Java 8 文档中所说,我们创建一个Programmer类,为了减少代码行数,使用Lombok注解添加Getter方法、全参构造方法以及toString()方法。按照文档规范,我也相应重写了hashCode()方法。以上两个方法的重写使用了Idea的代码自动生成功能生成。

代码如下:

@Getter
@ToString
@AllArgsConstructor
public class Programmer {

    private String name;
    private Integer age;

    @Override
    public boolean equals(Object o) {
        // 如果两个对象地址相等,则说明是同一个对象,返回true
        if (this == o) return true;
        // 如果形参对象o不是Programmer类型,则说明根本不可能和this对象相等,返回false
        if (!(o instanceof Programmer)) return false;
        // 如果o的类型是Programmer类型,则将o转换为Programmer对象
        Programmer that = (Programmer) o;
        // 如果两个Programmer对象的name属性不相等,则说明两个Programmer不相等,返回false
        if (!Objects.equals(name, that.name)) return false;
        // 如果两个Programmer对象的age属性不相等,则说明两个Programmer不相等,返回false
        // 如果两个Programmer对象的age属性相等,则说明两个Programmer的name和age属性都相等,两个对象相等,返回true
        return Objects.equals(age, that.age);
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + (age != null ? age.hashCode() : 0);
        return result;
    }
}

 这样,我们就可以将“==”与equals()方法区分开来。“==”用于比较地址是否相等,equals()方法用于比较对象是否相等。

二、hashCode()方法

1. hashCode()方法的作用

hashCode()方法是Java中Object类里自带的一个方法,具体描述如下所示:

hashCode

public int hashCode()

Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by HashMap.

The general contract of hashCode is:

  • Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
  • If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
  • It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.

As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the Java™ programming language.)

Returns:

a hash code value for this object.

—— Java 8 官方文档

 本人渣翻:

返回一个对象的哈希值。此方法是为了使用哈希表,例如HashMap。

哈希值的一般约定是:

  • 无论何时,当同一个对象在一次Java应用程序执行过程中多次调用该方法时,hashCode()方法在不修改equals()方法中需要比较的信息的前提下,必须都返回同一个整数。这个整数不需要在同一个应用程序的不同执行中保持一致。
  • 如果对象中的equals()方法的比较结果为相等,则两个对象的hashCode()方法应该返回两个相同的整数。
  • 如果两个对象根据equals()方法的比较结果为不相等,则不要求两个对象的hashCode()方法的返回值为不同的整数。但是,编程者应当意识到,不相等的对象具有不同的哈希值可能会提高哈希表的效率。

 Object类中定义的hashCode()方法会尽可能的为不同的对象返回不同的整数。(这通常是通过将对象的地址转换为整数来实现的,但是实现的技术并不需要使用到Java语言。)

返回值:

该对象的哈希值。

 因为Object类中的hashCode()方法的声明使用了native关键字,表名该方法不是使用Java语言来实现的,使用C或者C++来实现的(Java与C不得不说的二三事),因此不存在Java源码。

2. hashCode()方法的使用

因为hashCode()方法只需要返回一个int类型的整数即可,因此我们可以按照自己的习惯进行编写。因此理论上,你把所有类的hashCode()方法都重写为“return 0;”也是完全ok的。但是考虑到这样做会使哈希表的效

率大大降低(非常容易产生哈希冲突,导致哈希表上的一个节点会很长,哈希表退化为链表,查找效率降低为O(n)),因此一般会使用如下代码(借用上面的Programmer类):

@Override
public int hashCode() {
    int result = name != null ? name.hashCode() : 0;
    result = 31 * result + (age != null ? age.hashCode() : 0);
    return result;
}

 三、hashCode()方法和equals()方法在一些类中的使用

这里我是用HashSet进行举例,因为HashSet可以代表大部分使用了哈希表的数据结构。

我们都知道,HashSet中存储的对象是无序的,不重复的。而对象是否重复的就是通过equals()方法和hashCode()方法进行判断的。这就是为什么我们需要对equals()方法和hashCode()方法进行重载。

使用示例:

(1) 我们创建一个Programmer类,如下。

/**
 * @author Lee
 * @since 2022/7/11 14:40
 */
@Getter
@ToString
@AllArgsConstructor
public class Programmer {

    private String name;
    private Integer age;

}

此时我们使用如下代码进行测试。

/**
 * @author Lee
 * @since 2022/7/11 14:40
 */
public class TestJava {

    @Test
    public void testProgrammer() {
        Programmer programmer1 = new Programmer("李华", 23);
        Programmer programmer2 = new Programmer("李华", 23);
        System.out.println("programmer1 == programmer2 ? =======> "
            + (programmer1 == programmer2));
        System.out.println("programmer1.equals(programmer2) ? =======> "
            + (programmer1.equals(programmer2)));
        System.out.println("programmer1.hashCode() == programmer2.hashCode() ? =======> "
            + (programmer1.hashCode() == programmer2.hashCode()));
        HashSet<Programmer> set = new HashSet<>();
        set.add(programmer1);
        set.add(programmer2);
        System.out.println("set.size() =======> " + set.size());

    }

}

运行截图:

 

 (2) 我们对Programmer类的hashCode()方法进行重载,使它返回相同的哈希值。

// 在Programmer类中添加如下方法
@Override
public int hashCode() {
    return 1;
}

 同样使用上面的测试类进行测试。

运行截图:

 (3) 我们对Programmer类的hashCode()方法和equals()方法都进行重载,尽量使不同地址的对象都具有不同的哈希值,equals()方法对类中的所有属性都进行比较。

// 在Programmer类中增加equals()方法
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Programmer)) return false;
    Programmer that = (Programmer) o;
    if (!Objects.equals(name, that.name)) return false;
    return Objects.equals(age, that.age);
}

// 替换上一次的hashCode()方法
@Override
public int hashCode() {
    int result = name != null ? name.hashCode() : 0;
    result = 31 * result + (age != null ? age.hashCode() : 0);
    return result;
}

 运行截图:

 总结

对比上面三次对Programmer类的修改,我们不难发现,HashSet在判断两个对象是否相同时,首先调用hashCode()方法进行判断,如果集合内元素的哈希值与当前元素不一致,判断为不是相同的元素;相反,则暂时不做判断,调用equals()方法进行判断,equals()方法返回true,则为相同元素,反之则为不同元素。

所以,相同元素一定具有相同的哈希值,但是具有相同哈希值的元素不一定是相同元素。

结语

 本篇是对Java语言基础的查缺补漏。建议,Lombok虽然好,但是方法的基本原理和用处还是得清楚才行。我们使用工具是为了简化开发,方便开发,而不是让工具成为我们的限制。假若我们不明白其中缘由,当Lombok被ban时,或者说面试时,我们又该怎样呢?

文章中的翻译部分是我自己结合自己的理解和我个人蹩脚的英语翻译的,添加或者删除了一些词语。

如有翻译错误、不准确或者知识点错误还大佬指出,谢谢。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值