Object、String的hashCode()和equals()对比
一、概述
hashCode()处理流程如下图所示:将一块信息(或地址值或内容)经过算法处理转化成一个整数。可用于生成哈希码的信息可以是任意字节序列,字符,数字或它们的组合。设计一个哈希算法需要考虑输入数据的许多细节。
当需要在基于散列的集合(或容器)有效地存储、并检索数据时,哈希码会很有用。在将数据存储在容器中之前,我们计算其哈希码,然后将其存储在基于其哈希码的位置(也称为桶)。当要检索数据,其哈希码用于在容器中查找其位置,进行检索信息更快。值得注意的是,使用哈希码有效地检索数据是基于哈希码值在一个范围内的分布。如果生成的哈希码不一致分布时,数据检索效率可能不高。在最坏的情况下,数据的检索可能跟线性搜索存储在容器中的所有元素一样糟糕。如果你使用哈希函数,那么容器中的所有元素将存储在同一个存储桶中,这需要搜索所有元素。设计哈希函数使得它为我们提供均匀分布的哈希码对于实现高效而言至关重要,可用于基于散列的容器的快速数据查询。
Java中哈希码的用途是什么? Java使用哈希码的原因与上述相同:有效地从基于散列的集合中检索数据。如果你定义的类的对象未用作基于散列的键,例如,在HashSet,HashMap等集合中,我们也可以使用hashCode()求得对象的哈希码。
设计equals()和hashCode()一般需要一起重写,hashCode()采用对象的一些变量或内存地址设计哈希算法时,equals()也需要使用相同的变量或内存地址。
设计equals()需要遵循以下规则(对于三个非空对象x,y,z):
(1)反身性:x调用自身返回true
(2)对称性:x调用y 和 y调用x 返回结果都一样
(3)传递性:若x.equals(y)、y.equals(z)返回true,那么x.equals(z)返回true
(4)一致性:x调用y,无论调用多少次,返回结果都保持一致,除非x或y的信息有所改变
(5)与null的比较:任何类的非空对象与空引用的比较,始终返回false
(6)equals()与hashCode()的关系:
如果x.equals(y)返回true,则x.hashCode()和y.hashCode()返回值相同;
如果x和y的hashCode()返回值相同,则x和y不一定equal
二、查看源码
Object.java源码:
hashCode():
public native int hashCode();
equals():
public boolean equals(Object obj) {
return (this == obj);
}
hashCode()采用本地hashCode方法,采用默认的哈希值算法 将一个对象的地址转化为一个整数值(处理的是对象的地址值)
equals()比较两个引用是否指向同一个对象(地址值是否相同)
String.java源码
hashCode():
/**
* The value is used for character storage.
*
* @implNote This field is trusted by the VM, and is a subject to
* constant folding if String instance is constant. Overwriting this
* field after construction will cause problems.
*
* Additionally, it is marked with {@link Stable} to trust the contents
* of the array. No other facility in JDK provides this functionality (yet).
* {@link Stable} is safe here, because value is never null.
*/
@Stable
private final byte[] value;
/** Cache the hash code for the string */
private int hash; // Default to 0
/**
* Returns a hash code for this string. The hash code for a
* {@code String} object is computed as
* <blockquote><pre>
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* </pre></blockquote>
* using {@code int} arithmetic, where {@code s[i]} is the
* <i>i</i>th character of the string, {@code n} is the length of
* the string, and {@code ^} indicates exponentiation.
* (The hash value of the empty string is zero.)
*
* @return a hash code value for this object.
*/
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
hash = h = isLatin1() ? StringLatin1.hashCode(value)
: StringUTF16.hashCode(value);
}
return h;
}
equals():
/**
* Compares this string to the specified object. The result is {@code
* true} if and only if the argument is not {@code null} and is a {@code
* String} object that represents the same sequence of characters as this
* object.
*
* <p>For finer-grained String comparison, refer to
* {@link java.text.Collator}.
*
* @param anObject
* The object to compare this {@code String} against
*
* @return {@code true} if the given object represents a {@code String}
* equivalent to this string, {@code false} otherwise
*
* @see #compareTo(String)
* @see #equalsIgnoreCase(String)
*/
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
hashCode():不再是本地hashCode方法,采用自定义的哈希值算法 将一个对象的值转化为一个整数值(处理的是对象的内容)
equals():比较两个引用所指向的两个对象的值是否相同(内容是否相同);
这里的equals()的实现逻辑是:
(1)比较两个对象是否内存地址相同,如果相同,那么它们是同个对象、内容相同,返回true,否则继续判断;
(2)判断被调用对象是否是String类型(和调用对象一样),不是则返回false,否则继续判断;
(3)判断两个对象的value数组每一位是否相同,是则返回true,否则返回false
事实上,在(1)前面,也可以加上:
if (anObject == null) {
return false;
}
(4)先判断被调用对象是否为null,是的话则返回false。这是因为,equals()的调用对象不能是null,而如果调用对象是null,那么肯定不相同,无需进行(1)(2)(3)的过程。
三、测试代码
public static void main(String[] args) {
// Object类
System.out.println("---- Object类 ----");
Object obj = new Object();
Object obj1 = obj;
System.out.println("obj: " +obj+" obj1: "+obj1);
System.out.println("obj.hashCode(): "+obj.hashCode()+" obj1.hashCode(): "+obj1.hashCode());
System.out.println("obj==obj1: " + (obj == obj1));
System.out.println("obj.equals(obj1): " + obj.equals(obj1));
Object obj2 = new Object();
System.out.println("\n\nobj: " +obj+" obj2: "+obj2);
System.out.println("obj.hashCode(): "+obj.hashCode()+" obj2.hashCode(): "+obj2.hashCode());
System.out.println("obj==obj1: " + (obj == obj2));
System.out.println("obj.equals(obj1): " + obj.equals(obj2));
// String类
System.out.println("\n\n---- String类 ----");
String str = "Java";
String str1 = "Java";
System.out.println("str: "+str+"str1: "+str1);
System.out.println("str.hashCode(): "+str.hashCode()+" str1.hashCode(): "+str1.hashCode());
System.out.println("str==str1: "+(str == str1));
System.out.println("str.equals(str1): "+str.equals(str1));
String str2 = new String("Java");
System.out.println("\n\nstr: "+str+"str2: "+str2);
System.out.println("str.hashCode(): "+str.hashCode()+" str2.hashCode(): "+str2.hashCode());
System.out.println("str==str2: "+(str == str2));
System.out.println("str.equals(str2): "+str.equals(str2));
}
运行结果如下图所示:
可以看到,
(1)两个引用obj和obj1是指向同一个对象(地址值相同),obj和obj2则指向两个不同对象(地址值不同)。所以obj与obj1相同、hashCode值相同、==比较返回true、调用equals返回true,obj和obj2则相反
(2)引用str和str1指向同一个字符串对象(地址值相同、内容相同),str和str2则指向两个不同的字符串对象、但是它们的值相同(地址值不同、内容相同)。所以str和str1内容相同、hashCode相同、==比较返回true、调用equals返回true;
而str和str2则是内容相同、hashCode相同、==比较返回false、equals返回true
四、小结
1、Object的hashCode()是基于地址计算(同一性),String的hashCode()是基于内容(等同性),所以obj、obj1的hashCode相同、与obj2不同,而str、str1、str2的hashCode相同
2、Object的equals()基于地址比较,String的equals()基于内容,所以obj、obj1的equals互相调用返回true、与obj2则返回false,
而str、str1、str2的equals互相调用都返回true
3、不管是Object还是String,只要是引用类型,==对两个引用的比较都是基于地址
4、此外,System.identityHashCode()计算对象的哈希值也是基于地址计算
推荐阅读
-
说说hashCode() 和 equals() 之间的关系?
-
关于重写equals()和hashCode()的思考
-
一文搞懂hashCode()和equals()方法的原理
-
Java--equals和 == 的比较和equals()、HashCode()的重写
-
荐 java父类-Object类-equals与==-方法的重载和重写-游离块-this关键字
-
JAVA String.format的使用以及StringBuilder和String ‘+’的性能对比
-
Object、String的hashCode()和equals()对比
-
[C++ Primer Plus]学习笔记--关于C++ string和c类型字符数组的对比
-
【Java】请解释String类“==”和“equals”的区别
-
初识String类中的toString()和equals()