关于Java中的hashCode和equals方法

时间:2022-03-02 16:13:23

直接上源码!

public native 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
* {@link java.util.HashMap}.
* <p>
* The general contract of {@code hashCode} is:
* <ul>
* <li>Whenever it is invoked on the same object more than once during
* an execution of a Java application, the {@code hashCode} method
* must consistently return the same integer, provided no information
* used in {@code 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.
* <li>If two objects are equal according to the {@code equals(Object)}
* method, then calling the {@code hashCode} method on each of
* the two objects must produce the same integer result.
* <li>It is <em>not</em> required that if two objects are unequal
* according to the {@link java.lang.Object#equals(java.lang.Object)}
* method, then calling the {@code 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.
* </ul>
* <p>
* As much as is reasonably practical, the hashCode method defined by
* class {@code 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&trade; programming language.)
*
* @return a hash code value for this object.
* @see java.lang.Object#equals(java.lang.Object)
* @see java.lang.System#identityHashCode
*/

官方文档第一句话就说了,这个hashCode方法主要是为了哈希表而存在的,像HashSet,HashTable和HashMap都是使用哈希表的形式存储数据(Key Value),而hashCode计算出的hash值就可以唯一确定一个元素,有了hash值便可以快速定位元素,提高哈希表的性能。下面是hashCode规定的几点:

  • 在同一个Java应用程序中,只要是同一个对象,无论你调用hashCode方法多少次,它的返回值都应该是相同的。在不同的java应用程序中,这个返回值可以不同。
  • 如果两个对象通过equals方法判断出来是相同的,那么这两个对象调用hashCode方法的返回值也必须相同。
  • 如果两个对象通过调用*父类(Object)的equals方法判断出来是不相等的,那么这两个对象分别调用hashCode方法的返回值也必须是不同的。
  • 但是,作为程序员的我们可以在子类中重写这个方法,使得只要是对象中的值相等,那么这两个对象就相等,但是这可能会降低hash表的性能。(根据实际需求来判断是不是要重写吧)

基于这些原因,object类中的hashCode方法对于不同的对象必须返回不同的值(这是由内部转换方式决定的,通常这个值就是对象在JVM中的实际地址)

那这个值的取值肯定不都是对象实际所在的地址吧!比如说:8个基本数据类型的包装类的hashCode

  Boolean Byte Short Integer Long Character Float Doubles

下面我们来查看他们是怎么重写父类的方法的

  Boolean@Override

public int hashCode() {
return Boolean.hashCode(value);
}

/**
* Returns a hash code for a {@code boolean} value; compatible with
* {@code Boolean.hashCode()}.
*
* @param value the value to hash
* @return a hash code value for a {@code boolean} value.
* @since 1.8
*/
public static int hashCode(boolean value) {
return value ? 1231 : 1237;
}
可以看出,Boolean类型的变量,比较的是布尔值,如果都为true,那么返回1231,如果为false,那么返回1237,可以说只要
是Boolean类型的对象,只要值相同,那么他们就相同。

  Byte

@Override
public int hashCode() {
return Byte.hashCode(value);
}

/**
* Returns a hash code for a {@code byte} value; compatible with
* {@code Byte.hashCode()}.
*
* @param value the value to hash
* @return a hash code value for a {@code byte} value.
* @since 1.8
*/
public static int hashCode(byte value) {
return (int)value;
}
对于Byte类型的对象,hashCode的值就是他强转为int类型后的值

Integer,Short同理都是转化为int类型后的值

  Long
@Override
public int hashCode() {
return Long.hashCode(value);
}

/**
* Returns a hash code for a {@code long} value; compatible with
* {@code Long.hashCode()}.
*
* @param value the value to hash
* @return a hash code value for a {@code long} value.
* @since 1.8
*/
public static int hashCode(long value) {
return (int)(value ^ (value >>> 32));
}
返回值是其本身和他带符号右移后的数相异或得到的值再强转为int型。

  Character
直接调用Object类的hashCode方法
  Float
public static int hashCode(float value) {
return floatToIntBits(value);
}
public static int floatToIntBits(float value) {
int result = floatToRawIntBits(value);
// Check for NaN based on values of bit fields, maximum
// exponent and nonzero significand.
if ( ((result & FloatConsts.EXP_BIT_MASK) ==
FloatConsts.EXP_BIT_MASK) &&
(result & FloatConsts.SIGNIF_BIT_MASK) != 0)
result = 0x7fc00000;
return result;
}public static native int floatToRawIntBits(float value);
API上这样说:返回这个浮点对象的哈希代码。其结果是整数位表示,与方法floatToIntBits(float)所产生的一样,是由这个
浮点对象表示的原始浮点的值

  Double
与Floate型的差不多
API上这样说:返回此Double对象的哈希代码。结果是唯一的或两个半整数位表示的两个部分,完全由方法
doubleToLongBits(double)所产生

通过以上的这些话,可以很明确这个方法肯定和equals方法分不开。这不,源码中紧接着就是equals方法

public boolean equals(Object obj) {
return (this == obj);
}
/**
* Indicates whether some other object is "equal to" this one.
* <p>
* The {@code equals} method implements an equivalence relation
* on non-null object references:
* <ul>
* <li>It is <i>reflexive</i>: for any non-null reference value
* {@code x}, {@code x.equals(x)} should return
* {@code true}.
* <li>It is <i>symmetric</i>: for any non-null reference values
* {@code x} and {@code y}, {@code x.equals(y)}
* should return {@code true} if and only if
* {@code y.equals(x)} returns {@code true}.
* <li>It is <i>transitive</i>: for any non-null reference values
* {@code x}, {@code y}, and {@code z}, if
* {@code x.equals(y)} returns {@code true} and
* {@code y.equals(z)} returns {@code true}, then
* {@code x.equals(z)} should return {@code true}.
* <li>It is <i>consistent</i>: for any non-null reference values
* {@code x} and {@code y}, multiple invocations of
* {@code x.equals(y)} consistently return {@code true}
* or consistently return {@code false}, provided no
* information used in {@code equals} comparisons on the
* objects is modified.
* <li>For any non-null reference value {@code x},
* {@code x.equals(null)} should return {@code false}.
* </ul>
* <p>
* The {@code equals} method for class {@code Object} implements
* the most discriminating possible equivalence relation on objects;
* that is, for any non-null reference values {@code x} and
* {@code y}, this method returns {@code true} if and only
* if {@code x} and {@code y} refer to the same object
* ({@code x == y} has the value {@code true}).
* <p>
* Note that it is generally necessary to override the {@code hashCode}
* method whenever this method is overridden, so as to maintain the
* general contract for the {@code hashCode} method, which states
* that equal objects must have equal hash codes.
*
* @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
*/

直接比较两个对象是不是同一个对象

下面是重写equals方法需要满足的规则

  自反性:对于任何非空引用 x,x.equals(x) 应该返回 true

  对称性:对于任何引用 x 和 y,当且仅当 y.equals(x) 返回 truex.equals(y) 也应该返回 true

  传递性:对于任何引用 x、y 和 z,如果 x.equals(y)返回 truey.equals(z) 也应返回同样的结果

  一致性:如果 x 和 y 引用的对象没有发生变化,反复调用 x.equals(y) 应该返回同样的结果

  对于任意非空引用 x,x.equals(null) 应该返回 false

对于非空引用类型的变量,(在没有重写equals和hashCode的情况下)如果他们指向的在内存空间是同一个地址,那么他们就相等。重写equals方法最好也重写equals方法,主要的目的是保持和hashcode的关系(相同的对象必须拥有相同的hash值)。

重写了equals方法的几个包装类:File String Date 8个包装类,他们比较的都是类型及内容而不考虑引用是不是同一个对象,

如:

  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;
}
先比较是不是同一个对象,然后再比较长度,最后比较值,如果都满足相等,那么他们才是相等的

实例

public static void main(String[] args) {
String str1 = "BB";
String str2 = str1;
String str3 = new String("BB");
String str4 = new String("BB");

System.out.println(str1==str2);
System.out.println(str2==str3);
System.out.println(str3==str4);

System.out.println(str1.equals(str3));
System.out.println(str3.equals(str4));
}
结果:
  true
  false
  false
  true
  true
现在来试试自定义的类
  
public class Person {
Integer id;
String name;
}
这时我并没有重写equals方法
public static void main(String[] args) {
Person person1 = new Person(1, "小明");
Person person2 = new Person(1, "小明");

System.out.println(person1 == person2);
System.out.println(person1.equals(person2));
}
结果:
  false
  false
当我重写父类的equals方法后
再打印一次
  false
  true
怎么重写equals和hashCode呢

  大部分流行的几款ide都带有这个功能,当然也可以自己写,但是我就偷懒直接生成了

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

Person person = (Person) o;

if (id != null ? !id.equals(person.id) : person.id != null) {
return false;
}
return name != null ? name.equals(person.name) : person.name == null;
}

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

其中hashCode的返回值是自定义的,比如你可以将31改为32

Tips:了解过JVM后对这两个方法的理解会更透彻一点,结合源码食用