day05-hashCode理解

感谢作者:https://blog.csdn.net/fenglibing/article/details/8905007
感谢作者:https://www.cnblogs.com/dolphin0520/p/3681042.html
感谢作者:http://blog.csdn.net/chinayuan/article/details/3345559

hashCode理解

以下是关于HashCode的官方文档定义:

hashcode方法返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。 

hashCode 的常规协定是: 
在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。 
如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。 
以下情况不 是必需的:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。 
实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。) 

当equals方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

以上这段官方文档的定义,我们可以抽出成以下几个关键点:
1、hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;

2、如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同;

3、如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点;

4、两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。

以下两节文字感谢作者:http://blog.csdn.net/chinayuan/article/details/3345559

怎样理解hashCode的作用

以 java.lang.Object来理解,JVM每new一个Object,它都会将这个Object丢到一个Hash哈希表中去,这样的话,下次做 Object的比較或者取这个对象的时候,它会依据对象的hashcode再从Hash表中取这个对象。这样做的目的是提高取对象的效率。详细过程是这样:

1.new Object(),JVM依据这个对象的Hashcode值,放入到相应的Hash表相应的Key上,假设不同的对象确产生了同样的hash值,也就是发 生了Hash key同样导致冲突的情况,那么就在这个Hash key的地方产生一个链表,将全部产生同样hashcode的对象放到这个单链表上去,串在一起。
2.比較两个对象的时候,首先依据他们的 hashcode去hash表中找他的对象,当两个对象的hashcode同样,那么就是说他们这两个对象放在Hash表中的同一个key上,那么他们一 定在这个key上的链表上。

那么此时就仅仅能依据Object的equal方法来比較这个对象是否equal。当两个对象的hashcode不同的话,肯定 他们不能equal.

改写equals时总是要改写hashCode

java.lang.Object中对hashCode的约定:

  1. 在一个应用程序运行期间,假设一个对象的equals方法做比較所用到的信息没有被改动的话。则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。
  2. 假设两个对象依据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生同样的整数结果。
  3. 假设两个对象依据equals(Object o)方法是不相等的。则调用这两个对象中任一个对象的hashCode方法。不要求产生不同的整数结果。但假设能不同,则可能提高散列表的性能。

有一个概念要牢记。两个相等对象的equals方法一定为true, 但两个hashcode相等的对象不一定是相等的对象。
所以hashcode相等仅仅能保证两个对象在一个HASH表里的同一条HASH链上,继而通过equals方法才干确定是不是同一对象,假设结果为true, 则觉得是同一对象在插入。否则觉得是不同对象继续插入。

Object的代码:

public String toString () {
	return this.getClass().getName() + “@” + Integer.toHexString(this.hashCode());
}

public boolean equals (Object o) {
	return this == o;
}

public native int hashCode();

在有些情况下,程序设计者在设计一个类的时候为需要重写equals方法,比如String类,但是千万要注意,在重写equals方法的同时,必须重写hashCode方法。为什么这么说呢?
看一个例子:

class People{
    private String name;
    private int age;
     
    public People(String name,int age) {
    	this.name = name;this.age = age;
    }  
    public void setAge(int age){
    	this.age = age;
    }
         
    @Override
    public boolean equals(Object obj) {
        // TODO Auto-generated method stub
        return this.name.equals(((People)obj).name) && this.age== ((People)obj).age;
    }
}
 
public class Main {
    public static void main(String[] args) {
        People p1 = new People("Jack", 12);
        System.out.println(p1.hashCode());
       
        HashMap<People, Integer> hashMap = new HashMap<People, Integer>();
        hashMap.put(p1, 1);
         
        System.out.println(hashMap.get(new People("Jack", 12)));//null
    }
}

在这里我只重写了equals方法,也就说如果两个People对象,如果它的姓名和年龄相等,则认为是同一个人。
这段代码本来的意愿是想这段代码输出结果为“1”,但是事实上它输出的是“null”。为什么呢?原因就在于重写equals方法的同时忘记重写hashCode方法。虽然通过重写equals方法使得逻辑上姓名和年龄相同的两个对象被判定为相等的对象(跟String类类似),但是要知道默认情况下,hashCode方法是将对象的存储地址进行映射。那么上述代码的输出结果为“null”就不足为奇了。
原因很简单,p1指向的对象和System.out.println(hashMap.get(new People(“Jack”, 12)));这句中的new People(“Jack”, 12)生成的是两个对象,它们的存储地址肯定不同。

当上面同样的代码,当我们重写了hashCode方法时,这样一来的话,输出结果就为“1”了。
下面这段话摘自Effective Java一书:

* 在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。
* 如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。
* 如果两个对象根据equals方法比较是不等的,则hashCode方法不一定得返回不同的整数。

对于第二条和第三条很好理解,但是第一条,很多时候就会忽略。在《Java编程思想》一书中的P495页也有同第一条类似的一段话:

“设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该产生同样的值。
如果在讲一个对象用put()添加进HashMap时产生一个hashCode值,而用get()取出时却产生了另一个hashCode值,那么就无法获取该对象了。
所以如果你的hashCode方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()方法就会生成一个不同的散列码”。

hashCode与equals

再归纳一下就是hashCode是用于查找使用的,而equals是用于比较两个对象的是否相等的。以下这段话是从别人帖子回复拷贝过来的:

1.hashcode是用来查找的,如果你学过数据结构就应该知道,在查找和排序这一章有
例如内存中有这样的位置
0  1  2  3  4  5  6  7  
而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。
但如果用hashcode那就会使效率提高很多。
我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,然后把我们的类存放在取得得余数那个位置。比如我们的ID为998的余数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。这样,以后在查找该类时就可以通过ID除 8求余数直接找到存放的位置了。

2.但是如果两个类有相同的hashcode怎么办那(我们假设上面的类的ID不是唯一的),例如9除以817除以8的余数都是1,那么这是不是合法的,回答是:可以这样。那么如何判断呢?在这个时候就需要定义 equals了。
也就是说,我们先通过 hashcode来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过 equals 来在这个桶里找到我们要的类。
那么。重写了equals(),为什么还要重写hashCode()呢?
想想,你要在一个桶里找东西,你必须先要找到这个桶啊,你不通过重写hashcode()来找到桶,光重写equals()有什么用啊

最后,我们来看一个具体的示例吧,

public class HashTest {
	private int i;
 
	public int getI() {return i;}
 	public void setI(int i) {this.i = i;}
 
	public int hashCode() {
		return i % 10;
	}
 
	public final static void main(String[] args) {
		HashTest a = new HashTest();
		HashTest b = new HashTest();
		a.setI(1);      b.setI(1);
		Set<HashTest> set = new HashSet<HashTest>();
		set.add(a);     set.add(b);
		System.out.println(a.hashCode() == b.hashCode());//true
		System.out.println(a.equals(b));//false
		System.out.println(set);//[com.ubs.sae.test.HashTest@1, com.ubs.sae.test.HashTest@1]
	}
}

以上这个示例,我们只是重写了hashCode方法,从上面的结果可以看出,虽然两个对象的hashCode相等,但是实际上两个对象并不是相等;,我们没有重写equals方法,那么就会调用object默认的equals方法,是比较两个对象的引用是不是相同,显示这是两个不同的对象,两个对象的引用肯定是不定的。这里我们将生成的对象放到了HashSet中,而HashSet中只能够存放唯一的对象,也就是相同的(适用于equals方法)的对象只会存放一个,但是这里实际上是两个对象a,b都被放到了HashSet中,这样HashSet就失去了他本身的意义了。
此时我们把equals方法给加上:

public class HashTest {
	private int i;
 
	public int getI() {return i;}
 	public void setI(int i) {this.i = i;}
 
	public boolean equals(Object object) {
		if (object == null) {
			return false;
		}
		if (object == this) {
			return true;
		}
		if (!(object instanceof HashTest)) {
			return false;
		}
		HashTest other = (HashTest) object;
		if (other.getI() == this.getI()) {
			return true;
		}
		return false;
	}
 
	public int hashCode() {
		return i % 10;
	}
 
	public final static void main(String[] args) {
		HashTest a = new HashTest();
		HashTest b = new HashTest();
		a.setI(1);     b.setI(1);
		
		Set<HashTest> set = new HashSet<HashTest>();
		set.add(a);    set.add(b);
		System.out.println(a.hashCode() == b.hashCode());//true
		System.out.println(a.equals(b));//true
		System.out.println(set);//[com.ubs.sae.test.HashTest@1]
	}
}

内存地址与hashCode

感谢作者:https://www.cnblogs.com/dolphin0520/p/3681042.html
有些朋友误以为默认情况下,hashCode返回的就是对象的存储地址,事实上这种看法是不全面的,确实有些JVM在实现时是直接返回对象的存储地址,但是大多时候并不是这样,只能说可能存储地址有一定关联。
因此有人会说,可以直接根据hashcode值判断两个对象是否相等吗?肯定是不可以的,因为不同的对象可能会生成相同的hashcode值。虽然不能根据hashcode值判断两个对象是否相等,但是可以直接根据hashcode值判断两个对象不等,如果两个对象的hashcode值不等,则必定是两个不同的对象。如果要判断两个对象是否真正相等,必须通过equals方法。

也就是说对于两个对象,如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等;
如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同;
如果两个对象的hashcode值不等,则equals方法得到的结果必定为false;
如果两个对象的hashcode值相等,则equals方法得到的结果未知。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值