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就是一个很好地减小哈希碰撞次数的数值
。
总结
-
equals如果没有重写,那么默认比较的是地址是否相等
-
equals通常用来比较两个对象的内容是否相等,==用来比较两个对象的地址是否相等
-
重新equals一定要重写hashcode,hashcode相等,equals不一定相等,但是equals相等,hashcode一定相等
-
另外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 @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 "";
}
}