这个得从盘古开天说起。(篇幅较长)
- java中“==”与equals方法的区别
- hashcode方法
- 为什么java中在重写hashmap的时候要重写二者
一、 java中“==”与equals方法的区别
先看测试代码:
public class TestHashMethod {
public static void main(String[] args) {
String a = "a";
String b = "b";
String c = "a";
if(a == b){
System.out.println(a + " = " + b);
}else {
System.out.println(a + " ≠ " + b);
}
if(a.equals(b)){
System.out.println(a + " equals " + b);
}else {
System.out.println(a + " not equals " + b);
}
}
}
这样的代码没学过java的也知道结果。
下面的代码呢?
public class TestHashMethod {
public static void main(String[] args) {
String a = "a";
String b = "b";
String c = "a";
if(a == c){
System.out.println(a + " = " + b);
}else {
System.out.println(a + " ≠ " + b);
}
if(a.equals(c)){
System.out.println(a + " equals " + b);
}else {
System.out.println(a + " not equals " + b);
}
}
}
没学过java的肯定说,当然都是相等的。java掌握比较好的也说相等。java刚入门的新手可能觉得有点问题了(比如之前的我)。我依稀记得equals方法是比较值,而==是比较地址,所以可能不相等吧。其实是这样的:
在java中,“=”赋值的方式,是先创建了一个常量。比如:
String a = "A";
这里面,a是String类型的变量,A则是写在内存空间里的常量。我们不讨论JVM中如何给变量常量分配地址空间,我们只需要知道在这种情况下,JVM中标记它们的值是相同的。即使新创建一个变量b保存“A”,即:
String a = "A";
String b = "A";
最终得到的a,b的标识值相同,这里的标识值可以理解为内存地址。不过需要注意的是,这个内存时JVM的内存而非实际内存。通过以下方法查看:
public class TestHashMethod {
public static void main(String[] args) {
String a = "a";
String b = "a";
System.out.println(System.identityHashCode(a));
System.out.println(System.identityHashCode("a"));
System.out.println(System.identityHashCode(b));
}
}
而我们用到的"=="实际上也就是比较上述的标识值。
当然,在java中,还有另一种更常用创建对象的方式:
public class TestHashMethod {
public static void main(String[] args) {
String a = new String("a");
String b = new String("a");
if (a == b) {
System.out.println("a = b");
} else {
System.out.println("a ≠ b");
}
if (a.equals(b)) {
System.out.println("a equals b");
} else {
System.out.println("a not equals b");
}
System.out.println("a = " + a + "; b = " + b);
}
}
我们看,这里面利用new创建的对象,两者却不相等了。原因是,“a”永远只有一个,但是new String出来的“a”却可以存在无数个。每一个new出来的都拥有自己的标识值,也就是地址空间。换句话说,new String是将“a”又封装了一层,“a”永远不变,但是每次new String会一直在变。
public class TestHashMethod {
public static void main(String[] args) {
String a = new String("a");
String b = new String("a");
System.out.println(System.identityHashCode(a));
System.out.println(System.identityHashCode(b));
System.out.println(System.identityHashCode("a"));
}
}
从这里,我们也知道了,java中“==”比较的是这个标识值,那么equals呢?好像两个对象封装的值一样,equals结果就为真。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.
*
* @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 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;
}
源码中给出的注释,意思是,当且仅当二者不为空且字符顺序都相同时,返回真。
看源码,首先使用“==”判断二者标识值是否相同,相同时返回真。之后利用“instanceof”判断对象是否为字符类,如果不是则返回假。之后的操作便是逐字比较。
如果是自定义的类:
public class TestHashMethod {
private int a;
public TestHashMethod(int a){
this.a = a;
}
public static void main(String[] args) {
TestHashMethod a = new TestHashMethod(1);
TestHashMethod b = new TestHashMethod(1);
System.out.println(a.equals(b));
}
}
则会直接告诉你不相等。所以我们在比较自定义类的时候不能这么比较。
目前,我们了解了“==”与equals方法
二、hashcode方法
hashcode大家都知道,哈希值嘛,但是这个方法是做什么的?
public class TestHashMethod {
public static void main(String[] args) {
String a = "A";
int hashcode = a.hashCode();
System.out.println(a);
System.out.println(hashcode);
}
}
咦,好熟悉啊,我们好像在哪见过,你记得吗?对,ASCII码。接着,再试了几个其他值,发现这个hashcode方法就是转换ASCII码用的。那我们看看源码:
/**
* 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) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
有点天真,实际上,我们的求hashcode的算法并不是求ASCII码的算法,而是利用ASCII进行算法运算得出hashcode的方法。
我们再看看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™ programming language.)
*
* @return a hash code value for this object.
* @see java.lang.Object#equals(java.lang.Object)
* @see java.lang.System#identityHashCode
*/
public native int hashCode();
有点乱,直接看最后一行,这是个本地方法。
什么是本地方法?java中有两大类方法,一个是本地方法,一个是java方法。
简单说,本地方法的实现可能和java语言无关,例如可能是由C++实现的,本地方法不具有JVM的跨平台性,一个系统一种实现方法;而JAVA方法则是完完全全由java编写的。到这为止。
我们在使用hashmap一类的容器的时候,会声明key和value的类型,许多人以为存入hashmap的结构是这样的:
实际上他是这样的:
(https://blog.csdn.net/junchenbb0430/article/details/78643100图,侵删,懒的自己画了。。。)
hashcode通过对map大小取mod,hashcode%map.length,得到数组下标,存入。
这样的结构可以在检索的时候节约大量的时间,因为不用一个一个值去比对,而是先比较hashcode的值。
这样看一下:
public class TestHashMethod {
public static void main(String[] args) {
String a = "A";
String b = "A";
System.out.println(a.hashCode());
System.out.println(b.hashCode());
System.out.println("A".hashCode());
}
}
public class TestHashMethod {
public static void main(String[] args) {
String a = new String("A");
String b = new String("A");
System.out.println(a.hashCode());
System.out.println(b.hashCode());
System.out.println(new String("A").hashCode());
}
}
public class TestHashMethod {
private int a;
public TestHashMethod(int a){
this.a = a;
}
public static void main(String[] args) {
TestHashMethod a = new TestHashMethod(1);
TestHashMethod b = new TestHashMethod(1);
System.out.println(a.hashCode());
System.out.println(b.hashCode());
System.out.println(new TestHashMethod(1).hashCode());
}
}
问题出现了,我们自定义类的时候,即使为他们赋初值相同,得到的hashcode也不同。
也就是说,当使用非自定义类的时候,hashcode相等,值不一定相等;值相等,hashcode一定相等。而当使用自定义类求hashcode的时候,即使初始化相同值,最后hashcode也不等。
三、为什么java中在重写hashmap的时候要重写二者
其实,上面的内容已经破案了。如果不重写equals方法,则不能比较自定义类,在使用hashmap的时候就会出现问题,同理适用于hashcode,不重写就无法得出原hash值,也自然无法比较依靠原hashcode找到该数据。
public class TestHashMethod {
private int a;
public TestHashMethod(int a){
this.a = a;
}
@Override
public boolean equals(Object object){
if(object instanceof TestHashMethod){
if(((TestHashMethod) object).a == this.a){
return true;
}
}else {
return false;
}
return false;
}
@Override
public int hashCode(){
return a;
}
public static void main(String[] args) {
TestHashMethod a = new TestHashMethod(1);
TestHashMethod b = new TestHashMethod(1);
System.out.println(a.equals(b));
System.out.println(a.hashCode());
}
}