重新定义equals()和hashCode()方法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hxcaifly/article/details/84888886

1. 前言

在阅读Flink源码时,看到了很多类都自定义了equals()和hashCode()方法,并且这两个方法都是成对出现的。比如下面引自Flink的代码片段:

package org.apache.flink.runtime.leaderelection;

import java.util.UUID;

import static org.apache.flink.util.Preconditions.checkNotNull;

/**
 * A combination of a leader address and leader id.
 */
public class LeaderAddressAndId {

	private final String leaderAddress;
	private final UUID leaderId;

	public LeaderAddressAndId(String leaderAddress, UUID leaderId) {
		this.leaderAddress = checkNotNull(leaderAddress);
		this.leaderId = checkNotNull(leaderId);
	}

	// ------------------------------------------------------------------------

	public String leaderAddress() {
		return leaderAddress;
	}

	public UUID leaderId() {
		return leaderId;
	}

	// ------------------------------------------------------------------------

	@Override
	public int hashCode() {
		return 31 * leaderAddress.hashCode()+ leaderId.hashCode();
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) {
			return true;
		}
		else if (o != null && o.getClass() == LeaderAddressAndId.class) {
			final LeaderAddressAndId that = (LeaderAddressAndId) o;
			return this.leaderAddress.equals(that.leaderAddress) && this.leaderId.equals(that.leaderId);
		}
		else {
			return false;
		}
	}

	@Override
	public String toString() {
		return "LeaderAddressAndId (" + leaderAddress + " / " + leaderId + ')';
	}
}

这里我们有必要弄懂equals()和hashCode()方法。

2. equals()和hashCode()用法

当我们写了一个类的时候,我们一般要重写这个类的equals方法,因为所有类都继承Object,而Object中的equals方法是这么写的:

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

也就是Object中的equals方法实际上比较的是两个对象是否是同一个(包含内存地址的相等),显然当我们只想比较两个对象的数值是否相等的时候这不是我们所想要的。

假如现在我们有一个类PersonWithOverride,有private int age和private String name两个属性,我们想要比较两个PersonWithOverride类生成的对象是否相等,我们就需要重写equals方法,看看这个equals方法是如何重写的:

@Override
public boolean equals(Object obj) {
    if (this == obj)//如果是同一个对象 直接返回true
        return true;
    if (obj == null)//如果比较的为null 直接返回false
        return false;
    if (getClass() != obj.getClass())//两者类类型不一样直接返回false
        return false;
    PersonWithOverride other = (PersonWithOverride) obj;//类型转换
    if (age != other.age)//判断数据是否相等
        return false;
    if (name == null) {//对于对象内的对象比较 由于要使用equals 也要注意避免出现空指针异常
        if (other.name != null)
            return false;
    } else if (!name.equals(other.name))//不会出现空指针异常 因为前面判断了
        return false;
    return true;
}

这样就重写了一个考虑得比较全面的equals方法,但是我们知道重写equals方法还需要重写hashCode方法,这是为什么呢?

首先我们需要知道hashCode的作用,java中的hashCode的作用主要是用增加哈希存储结构(HashMap HashTable之流)的查找速度,这些哈希存储结构可以通过hashCode来确定对象在哈希存储结构中的存储位置。通过这么一个作用的描述,我们应该get到以下几点:

  1. hashCode作用主要在于增加数据在哈希家族中的查询速度。
  2. 如果hashCode相等,他们在哈希结构中存储位置相等,但是不是同一个对象!换句话说hashCode相等,调用equals不一定相等。
  3. 如果equals相等,那么他们的存储位置当然相等,所以hashCode一定也是相等的。
  4. 根据2、3两条,重写equals方法就一定要重写hashCode方法。

嗯,看到了这里,我要就是重写了equals但是就是不重写hashCode呢?我们来测试下:

首先写一个PersonWithoutOverride类,重写equals,但是不重写hashCode:

package test;
 
public class PersonWithoutOverride {
    private int age;
    private String name;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public PersonWithoutOverride() {
        super();
    }
    public PersonWithoutOverride(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)//如果是同一个对象 直接返回true
            return true;
        if (obj == null)//如果比较的为null 直接返回false
            return false;
        if (getClass() != obj.getClass())//两者类类型不一样直接返回false
            return false;
        PersonWithoutOverride other = (PersonWithoutOverride) obj;//类型转换
        if (age != other.age)//判断数据是否相等
            return false;
        if (name == null) {//对于对象内的对象比较 也要注意避免出现空指针异常
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))//不会出现空指针异常 因为前面判断了
            return false;
        return true;
    }
}

然后我们在测试类里用HashMap测试一下:

package test;
 
import java.util.HashMap;
import java.util.Map;
 
public class Test {
    public static void main(String[] args) {
        PersonWithoutOverride pn1 = new PersonWithoutOverride(1,"test");
        PersonWithoutOverride pn2 = new PersonWithoutOverride(1,"test");
        Map<Object,String> testMap = new HashMap<Object,String>();
        testMap.put(pn1, "1");
        System.out.println(testMap.get(pn2));//null
    }
}

然后我们就会发现testMap.get(pn2)竟然是null。为什么呢?因为在java的哈希系列存储结构中,get(Object obj)方法首先会根据obj.hashCode()找到obj这个对象在这个哈希存储结构中的存储位置,然后比较obj和这个存储位置上的元素是否相等,这样就增加了效率,如果hashCode不一样,说明这个key值和obj不对应(毕竟存储位置都不一样,对象当然不一样);如果hashCode一样,然后才调用equals方法,判断这个位置存储的哪个元素是自己要寻找的,或者这个位置存储的所有元素都不是要找的obj。所以当我们没有重写hashCode方法的时候,虽然get(Object obj)中的obj和需要判断的key对象是相等的,但是hashCode不一样,于是便不能找到obj这个key的位置,当然也就找不到对应的value。

那么正确的方法是什么样的呢:

 @Override
    public int hashCode() {
        final int prime = 31;//为什么是31?因为这个数需要是质数 31是经验验证的一个能够很好地减小哈希碰撞的质数
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

把这个重写的hashCode方法放到类下就可以了,这样就能保证equals返回为true的两个对象一定是hashCode相等的。这里为什么有一个final in prime = 31呢? 因为需要一个质数来进行类似的计算,以此来减小哈希碰撞机会。设想一下,如果许多对象的hashCode都是相等的,那么一个存储地址就会有许多不同的对象,而其他地方却是空着的,这是很不合理的,而31就是一个很好地减小哈希碰撞次数的数值。

猜你喜欢

转载自blog.csdn.net/hxcaifly/article/details/84888886