java equals方法和hashCode方法

https://blog.youkuaiyun.com/qq_35580883/article/details/78514288

equals

当我们写了一个类的时候,我们一般要重写这个类的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;
    }

hashcode

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

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

1、hashCode作用主要在于增加数据在哈希家族中的查询速度

2、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。

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

@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就是一个很好地减小哈希碰撞次数的数值

总结

  1. equals如果没有重写,那么默认比较的是地址是否相等

  2. equals通常用来比较两个对象的内容是否相等,==用来比较两个对象的地址是否相等

  3. 重新equals一定要重写hashcode,hashcode相等,equals不一定相等,但是equals相等,hashcode一定相等

  4. 另外equals和hashcode的篇幅比较大,可以使用lombok的注解@EqualsAndHashCode,在编译的时候,自动生成equals和hashcode

    @EqualsAndHashCode使用说明:

  • 元注解:@Target(ElementType.TYPE)只能在类上使用,
    @Retention(RetentionPolicy.SOURCE)只在代码里保留。
  • 注解属性:
    • exclude,of不用了解,准备弃用了,使用EqualsAndHashCode.Exclude和EqualsAndHashCode.Include代替;
    • callSuper表示是否将父类的equals和hashCode方法加到该子类的equals和hashCode方法中;
    • doNotUseGetters表示是否使用getter访问成员变量;
    • onParam参考Lombok实验室之onX使用;
    • onlyExplicitlyIncluded为false时,所有的非静态和非瞬态的字段都会被包含进equals和hashCode方法中;为true时,只有在字段上明确使用了EqualsAndHashCode.Include注解才会被包含进equals和hashCode方法中。
package lombok;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Generates implementations for the {@code equals} and {@code hashCode} methods inherited by all objects, based on relevant fields.
 * <p>
 * Complete documentation is found at <a href="https://projectlombok.org/features/EqualsAndHashCode">the project lombok features page for &#64;EqualsAndHashCode</a>.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface EqualsAndHashCode {
	/**
	 * Any fields listed here will not be taken into account in the generated {@code equals} and {@code hashCode} implementations.
	 * Mutually exclusive with {@link #of()}.
	 * <p>
	 * Will soon be marked {@code @Deprecated}; use the {@code @EqualsAndHashCode.Exclude} annotation instead.
	 * 
	 * @return A list of fields to exclude.
	 */
	String[] exclude() default {};
	
	/**
	 * If present, explicitly lists the fields that are to be used for identity.
	 * Normally, all non-static, non-transient fields are used for identity.
	 * <p>
	 * Mutually exclusive with {@link #exclude()}.
	 * <p>
	 * Will soon be marked {@code @Deprecated}; use the {@code @EqualsAndHashCode.Include} annotation together with {@code @EqualsAndHashCode(onlyExplicitlyIncluded = true)}.
	 * 
	 * @return A list of fields to use (<em>default</em>: all of them).
	 */
	String[] of() default {};
	
	/**
	 * Call on the superclass's implementations of {@code equals} and {@code hashCode} before calculating for the fields in this class.
	 * <strong>default: false</strong>
	 * 
	 * @return Whether to call the superclass's {@code equals} implementation as part of the generated equals algorithm.
	 */
	boolean callSuper() default false;
	
	/**
	 * Normally, if getters are available, those are called. To suppress this and let the generated code use the fields directly, set this to {@code true}.
	 * <strong>default: false</strong>
	 * 
	 * @return If {@code true}, always use direct field access instead of calling the getter method.
	 */
	boolean doNotUseGetters() default false;
	
	/**
	 * Any annotations listed here are put on the generated parameter of {@code equals} and {@code canEqual}.
	 * This is useful to add for example a {@code Nullable} annotation.<br>
	 * The syntax for this feature depends on JDK version (nothing we can do about that; it's to work around javac bugs).<br>
	 * up to JDK7:<br>
	 *  {@code @EqualsAndHashCode(onParam=@__({@AnnotationsGoHere}))}<br>
	 * from JDK8:<br>
	 *  {@code @EqualsAndHashCode(onParam_={@AnnotationsGohere})} // note the underscore after {@code onParam}.
	 *  
	 * @return List of annotations to apply to the generated parameter in the {@code equals()} method.
	 */
	AnyAnnotation[] onParam() default {};
	
	/**
	 * Placeholder annotation to enable the placement of annotations on the generated code.
	 * @deprecated Don't use this annotation, ever - Read the documentation.
	 */
	@Deprecated
	@Retention(RetentionPolicy.SOURCE)
	@Target({})
	@interface AnyAnnotation {}
	
	/**
	 * Only include fields and methods explicitly marked with {@code @EqualsAndHashCode.Include}.
	 * Normally, all (non-static, non-transient) fields are included by default.
	 * 
	 * @return If {@code true}, don't include non-static non-transient fields automatically (default: {@code false}).
	 */
	boolean onlyExplicitlyIncluded() default false;
	
	/**
	 * If present, do not include this field in the generated {@code equals} and {@code hashCode} methods.
	 */
	@Target(ElementType.FIELD)
	@Retention(RetentionPolicy.SOURCE)
	public @interface Exclude {}
	
	/**
	 * Configure the behaviour of how this member is treated in the {@code equals} and {@code hashCode} implementation; if on a method, include the method's return value as part of calculating hashCode/equality.
	 */
	@Target({ElementType.FIELD, ElementType.METHOD})
	@Retention(RetentionPolicy.SOURCE)
	public @interface Include {
		/**
		 * Defaults to the method name of the annotated member.
		 * If on a method and the name equals the name of a default-included field, this member takes its place.
		 * 
		 * @return If present, this method serves as replacement for the named field.
		 * 是指当一个方法的名称和变量名相同时,包含方法进入equals,不包含字段。
		 */
		String replaces() default "";
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值