重写equals和hashCode方法



1. 来源

Object类中定义了equalhashCode方法,又因为Object是基类,所以继承了Object的类都有这两个方法


先来看看Object类中的equal方法

* @param   obj   the reference object with which to compare.
* @return  {@code true} if this object is the same as the obj
*          argument; {@code false} otherwise.
* @see     #hashCode()
* @see     java.util.HashMap
*/
public boolean equals(Object obj) {
	return (this == obj);
}

从中可以看出,Object类中的equal方法是用 ==来比较的,即二者地址是否相同,这样比较即判断二者是否同一对象



2. 需求

若要比较对象的内部是否相等,而不是比较是否同一对象,该怎么办?有没有想法?其实我们日常也经常使用这种比较,只是没有注意到而已,没错那就是字符串,String.equals( ),虽然不是同一对象,但只要内容相同,就返回true,即:"123".equals("123") == true,那么来看看String内部是如何实现这种功能的


String内部的equals方法

public boolean equals(Object anObject) {
    if (this == anObject) {		// 首先比较地址,如果地址相同,那肯定是同一对象同内容
        return true;
    }
    if (anObject instanceof String) {	// 再判断类型,只有类型相同才能比较内部的值
        String anotherString = (String)anObject;	// 向下转型
        int n = value.length;
        if (n == anotherString.value.length) {	// 比较二者字符串长度,长度不同,内容肯定不同
            char v1[] = value;		// 这里也可以看出字符串底层是数组实现
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {		// 逐一比较二者底层数组的每一个字符
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}


3. 重写equals、hashCode方法

自定义的类该怎么实现equal方法呢?这里得遵循如下规则

  • 两对象若equals相同,则hashCode方法返回值也得相同
  • 两个对象的hashCode返回值相同二者equals不一定相同

从该规则可以知道,重写equals必须重写hashCode方法,因为hashCode是对堆内存的对象产生的特殊值,如果没有重写,不同对象产生的哈希值基本是不同的(哈希碰撞)。

HashMap中是先用Key的hashCode来获取下标,然后才判断hashCode,再判断地址==,最后才判断equals。

Object的hashCode是native方法,所以不放出源码了,下面直接挂出重写equal的代码(仿照String)。


重写自定义类的equals、hashCode方法

public class User {
    private String firstName;
    private String lastName;
    private int age;

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj instanceof User) {
            User user = (User) obj;

            // 有引用类型和基本类型的区别
            // 引用类型可以为null,只有双方为null才认为是true
            return Objects.equals(this.firstName, user.firstName)
                    && Objects.equals(this.lastName, user.lastName)
                    && this.age == user.age;
        }
        return false;
    }

    @Override
    public int hashCode() {
        int h = 0;

        // 字符串已经正确实现了hashCode方法
        // int本身是整型,稍作处理后可以作为hashCode
        // * 31 为了将hashCode平均分布到整个int范围
        h = 31 * h + firstName.hashCode();
        h = 31 * h + lastName.hashCode();
        h = 31 * h + age;
        return h;
    }

    public User(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    public static void main(String[] args) {
        User user1 = new User("Liu","Howl",20);
        User user2 = new User("Liu","Howl",20);
        User user3 = new User("Liu","Howlet",20);

        System.out.println(user1.equals(user2));
        System.out.println(user1.equals(user3));
    }
}
true
false


4. 验证集合是否先判断hashCode

当然要使用唯一的集合,这里举例hashSet,还是使用上面的代码


public static void main(String[] args) {
		
    User user1 = new User("Liu","Howl",20);
    User user2 = new User("Liu","Howl",20);

    HashSet<User> hashSet = new HashSet<User>();

    hashSet.add(user1);
    hashSet.add(user2);

    Iterator iterator = hashSet.iterator();
    while(iterator.hasNext()){
        System.out.println(iterator.next());
    }

    System.out.println(user1.hashCode());
    System.out.println(user2.hashCode());
}
User [name=Howl]
2255420
2255420

发现集合只有一个对象,即判断user1,user2为同一对象了


举个反例,即注释掉User类重写的hashCode,再重新运行上面的代码,输出如下

User [name=Howl]
User [name=Howl]
366712642
1829164700

这样就可以验证集合确实是对hashCode来判断二者是否相等的,所以这里得十分十分十分注意,以后使用集合存储对象,如果要判断是否相等,考虑重写equal和hashCode方法


原文地址:https://www.cnblogs.com/Howlet/p/12259639.html