Java为什么要重写equals方法和hashcode方法(新手场)

这个得从盘古开天说起。(篇幅较长)

  1. java中“==”与equals方法的区别
  2. hashcode方法
  3. 为什么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&trade; 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());
	}
}

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值