学习Java编程equals()和hashCode()方法

equals()和hashCode()区别?

equals():反映的是对象或变量具体的值,即两个对象里面包含的值--可能是对象的引用,也可能是值类型的值。

hashCode():计算出对象实例的哈希码,并返回哈希码,又称为散列函数。根类Object的hashCode()方法的计算依赖于对象实例的D(内存地址),故每个Object对象的hashCode都是唯一的;当然,当对象所对应的类重写了hashCode()方法时,结果就截然不同了。

之所以有hashCode方法,是因为在批量的对象比较中,hashCode要比equals来得快,很多集合都用到了hashCode,比如HashTable。

两个obj,如果equals()相等,hashCode()一定相等。

两个obj,如果hashCode()相等,equals()不一定相等(Hash散列值有冲突的情况,虽然概率很低)。

所以:

可以考虑在集合中,判断两个对象是否相等的规则是:

第一步,如果hashCode()相等,则查看第二步,否则不相等;

第二步,查看equals()是否相等,如果相等,则两obj相等,否则还是不相等。

1、首先equals()和hashcode()这两个方法都是从object类中继承过来的。

equals()是对两个对象的地址值进行的比较(即比较引用是否相同)。

hashCode()是一个本地方法,它的实现是根据本地机器相关的。

2、Java语言对equals()的要求如下,这些要求是必须遵循的:

A对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。

B反射性:x.equals(x)必须返回是“true”。

C类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。

D一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。

任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。

3、equals()相等的两个对象,hashcode()一定相等;

反过来:hashcode()不等,一定能推出equals()也不等;

hashcode()相等,equals()可能相等,也可能不等。

为什么选择hashcode方法?

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

1.newObject(),JVM根据这个对象的Hashcode值,放入到对应的Hash表对应的Key上,如果不同的对象确产生了相同的hash值,也就是发生了Hashkey相同导致冲突的情况,那么就在这个Hashkey的地方产生一个链表,将所有产生相同hashcode的对象放到这个单链表上去,串在一起。

2.比较两个对象的时候,首先根据他们的hashcode去hash表中找他的对象,当两个对象的hashcode相同,那么就是说他们这两个对象放在Hash表中的同一个key上,那么他们一定在这个key上的链表上。那么此时就只能根据Object的equal方法来比较这个对象是否equal。当两个对象的hashcode不同的话,肯定他们不能equal.

下面我举个例子详细说明下。

list是可以重复的,set是不可以重复的。那么set存储数据的时候是怎样判断存进的数据是否已经存在。使用equals()方法呢,还是hashcode()方法。

假如用equals(),那么存储一个元素就要跟已存在的所有元素比较一遍,比如已存入100个元素,那么存101个元素的时候,就要调用equals方法100次。

但如果用hashcode()方法的话,他就利用了hash算法来存储数据的。这样的话每存一个数据就调用一次hashcode()方法,得到一个hashcode值及存入的位置。如果该位置不存在数据那么就直接存入,否则调用一次equals()方法,不相同则存,相同不存。这样下来整个存储下来不需要调用几次equals方法,虽然多了几次hashcode方法,但相对于前面来讲效率高了不少。

为什么要重写equals方法?

因为Object的equal方法默认是两个对象的引用的比较,意思就是指向同一内存,地址则相等,否则不相等;如果你现在需要利用对象里面的值来判断是否相等,则重载equal方法。

说道这个地方我相信很多人会有疑问,相信大家都被String对象的equals()方法和"=="纠结过一段时间,当时我们知道String对象中equals方法是判断值的,而==是地址判断。

那照这么说equals怎么会是地址的比较呢?

那是因为实际上JDK中,String、Math等封装类都对Object中的equals()方法进行了重写。

我们先看看Object中equals方法的源码:

1.publicbooleanequals(Objectobj){

2.return(this==obj);

3.}

我们都知道所有的对象都拥有标识(内存地址)和状态(数据),同时“==”比较两个对象的的内存地址,所以说使用Object的equals()方法是比较两个对象的内存地址是否相等,即若object1.equals(object2)为true,则表示equals1和equals2实际上是引用同一个对象。虽然有时候Object的equals()方法可以满足我们一些基本的要求,但是我们必须要清楚我们很大部分时间都是进行两个对象的比较,这个时候Object的equals()方法就不可以了,所以才会有String这些类对equals方法的改写,依次类推Double、Integer、Math。。。。等等这些类都是重写了equals()方法的,从而进行的是内容的比较。希望大家不要搞混了。

改写equals时总是要改写hashcode

这一点上《Effectivejava》中点7条和第8条讲的很详细,如果看完该博客后你还不懂得话,可以看看。里面也涉及到hashcode的设计,由于内容较深,我觉得我还接受不了,所以就不讨论这个了。

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

1.在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。

2.如果两个对象根据equals(Objecto)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。

3.如果两个对象根据equals(Objecto)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。

根据上一个问题,实际上我们已经能很简单的解释这一点了,比如改写String中的equals为基于内容上的比较而不是内存地址的话,那么虽然equals相等,但并不代表内存地址相等,由hashcode方法的定义可知内存地址不同,没改写的hashcode值也可能不同。所以违背了第二条约定。

又如new一个对象,再new一个内容相等的对象,调用equals方法返回的true,但他们的hashcode值不同,将两个对象存入HashSet中,会使得其中包含两个相等的对象,因为是先检索hashcode值,不等的情况下才会去比较equals方法的。

hashCode方法使用介绍

Hash表数据结构常识:

哈希表基于数组。

缺点:基于数组的,数组创建后难以扩展。某些哈希表被基本填满时,性能下降得非常严重。

没有一种简便得方法可以以任何一种顺序遍历表中数据项。

如果不需要有序遍历数据,并且可以提前预测数据量的大小,那么哈希表在速度和易用性方面是无与伦比的。

为什么HashCode对于对象是如此的重要:

一个对象的HashCode就是一个简单的Hash算法的实现,虽然它和那些真正的复杂的Hash算法相比还不能叫真正的算法,它如何实现它,不仅仅是程序员的编程水平问题,而是关系到你的对象在存取是性能的非常重要的关系.有可能,不同的HashCode可能会使你的对象存取产生,成百上千倍的性能差别.

先来看一下,在Java中两个重要的数据结构:HashMap和Hashtable,虽然它们有很大的区别,如继承关系不同,对value的约束条件(是否允许null)不同,以及线程安全性等有着特定的区别,但从实现原理上来说,它们是一致的.所以,我们只以Hashtable来说明:

在java中,存取数据的性能,一般来说当然是首推数组,但是在数据量稍大的容器选择中,Hashtable将有比数组性能更高的查询速度.具体原因看下面的内容.

Hashtable在存储数据时,一般先将该对象的HashCode和0x7FFFFFFF做与操作,因为一个对象的HashCode可以为负数,这样操作后可以保证它为一个正整数.然后以Hashtable的长度取模,得到该对象在Hashtable中的索引.

index=(o.hashCode()&0x7FFFFFFF)%hs.length;

这个对象就会直接放在Hashtable的每index位置,对于写入,这和数组一样,把一个对象放在其中的第index位置,但如果是查询,经过同样的算法,Hashtable可以直接从第index取得这个对象,而数组却要做循环比较.所以对于数据量稍大时,Hashtable的查询比数组具有更高的性能.既然一个对象可以根据HashCode直接定位它在Hashtable中的位置,那么为什么Hashtable还要用key来做映射呢?这就是关系Hashtable性能问题的最重要的问题:Hash冲突.

常见的Hash冲突是不同对象最终产生了相同的索引,而一种非常甚至绝对少见的Hash冲突是,如果一组对象的个数大过了int范围,而HashCode的长度只能在int范围中,所以肯定要有同一组的元素有相同的HashCode,这样无论如何他们都会有相同的索引.当然这种极端的情况是极少见的,可以暂不考虑,但是对于同的HashCode经过取模,则会产中相同的索引,或者不同的对象却具有相同的HashCode,当然具有相同的索引.所以对于索引相同的对象,在该index位置存放了多个值,这些值要想能正确区分,就要依靠key来识别.

事实上一个设计各好的HashTable,一般来说会比较平均地分布每个元素,因为Hashtable的长度总是比实际元素的个数按一定比例进行自增(装填因子一般为0.75)左右,这样大多数的索引位置只有一个对象,而很少的位置会有几个元素.所以Hashtable中的每个位置存放的是一个链表,对于只有一个对象是位置,链表只有一个首节点(Entry),Entry的next为null.然后有hashCode,key,value属性保存了该位置的对象的HashCode,key和value(对象本身),如果有相同索引的对象进来则会进入链表的下一个节点.如果同一个索引中有多个对象,根据HashCode和key可以在该链表中找到一个和查询的key相匹配的对象.

从上面我看可以看到,对于HashMap和Hashtable的存取性能有重大影响的首先是应该使该数据结构中的元素尽量大可能具有不同的HashCode,虽然这并不能保证不同的HashCode产生不同的index,但相同的HashCode一定产生相同的index,从而影响产生Hash冲突.对于一个象,如果具有很多属性,把所有属性都参与散列,显然是一种笨拙的设计.因为对象的HashCode()方法几乎无所不在地被自动调用,如equals比较,如果太多的对象参与了散列.那么需要的操作常数时间将会增加很大.所以,挑选哪些属性参与散列绝对是一个编程水平的问题.从实现来说,一般的HashCode方法会这样:

returnAttribute1.HashCode()Attribute1.HashCode()..[super.HashCode()],

我们知道,每次调用这个方法,都要重新对方法内的参与散列的对象重新计算一次它们的HashCode的运算,如果一个对象的属性没有改变,仍然要每次都进行计算,所以如果设置一个标记来缓存当前的散列码,只要当参与散列的对象改变时才重新计算,否则调用缓存的hashCode,这可以从很大程度上提高性能.

默认的实现是将对象内部地址转化为整数作为HashCode,这当然能保证每个对象具有不同的HasCode,因为不同的对象内部地址肯定不同(废话),但java语言并不能让程序员获取对象内部地址,所以,让每个对象产生不同的HashCode有着很多可研究的技术.

如果从多个属性中采样出能具有平均分布的hashCode的属性,这是一个性能和多样性相矛盾的地方,如果所有属性都参与散列,当然hashCode的多样性将大大提高,但牺牲了性能,而如果只能少量的属性采样散列,极端情况会产生大量的散列冲突,如对"人"的属性中,如果用性别而不是姓名或出生日期,那将只有两个或几个可选的hashcode值,将产生一半以上的散列冲突.所以如果可能的条件下,专门产生一个序列用来生成HashCode将是一个好的选择(当然产生序列的性能要比所有属性参与散列的性能高的情况下才行,否则还不如直接用所有属性散列).

如何对HashCode的性能和多样性求得一个平衡,可以参考相关算法设计的书,其实并不一定要求非常的优秀,只要能尽最大可能减少散列值的聚集.重要的是我们应该记得HashCode对于我们的程序性能有着重要的影响,在程序设计时应该时时加以注意.

请记住:如果你想有效的使用HashMap,你就必须重写在其的HashCode()。

还有两条重写HashCode()的原则:

不必对每个不同的对象都产生一个唯一的hashcode,只要你的HashCode方法使get()能够得到put()放进去的内容就可以了。即“不为一原则”。生成hashcode的算法尽量使hashcode的值分散一些,不要很多hashcode都集中在一个范围内,这样有利于提高HashMap的性能。即“分散原则”。至于第二条原则的具体原因,有兴趣者可以参考BruceEckel的《ThinkinginJava》,在那里有对HashMap内部实现原理的介绍,这里就不赘述了。掌握了这两条原则,你就能够用好HashMap编写自己的程序了。不知道大家注意没有,java.lang.Object中提供的三个方法:clone(),equals()和hashCode()虽然很典型,但在很多情况下都不能够适用,它们只是简单的由对象的地址得出结果。这就需要我们在自己的程序中重写它们,其实java类库中也重写了千千万万个这样的方法。利用面向对象的多态性——覆盖,Java的设计者很优雅的构建了Java的结构,也更加体现了Java是一门纯OOP语言的特性。

Java提供的Collection和Map的功能是十分强大的,它们能够使你的程序实现方式更为灵活,执行效率更高。希望本文能够对大家更好的使用HashMap有所帮助。

 为了让学习变得轻松、高效,今天给大家免费分享一套Java入门教学资源。帮助大家在成为Java架构师的道路上披荆斩棘。需要资料的欢迎加入学习交流群:9285,05736

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/503175.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java技术依然备受追捧,主要学习内容有哪些?

Java技术依然备受追捧,主要学习内容有哪些?Java 语言是一门随时代快速发展的计算机语言程序,其深刻展示了程序编写的精髓,加上其简明严谨的结构及简洁的语法编写为其将来的发展及维护提供了保障。 在众多的主流编程语言中&#x…

六本JAVA架构书,构建科学得架构知识体系

今天把我看过的 6 本架构方面的书都推荐给大家,总有一本满足你的需要。 1. 《大型网站技术架构:核心原理与案例分析》 这是比较早,比较系统介绍大型网站技术架构的书,通俗易懂又充满智慧,即便你之前完全没接触过网站开…

最难学的十大编程语言,Java排第二,它竟是第一名!

开发者经济分析公司 SlashData 曾发布了《 2018 年第 2 季度各国开发者状况》调查报告,共有 167 个国家的 20500 多名开发者参与其中。 报告面向全球所有类型的程序员,对使用每种主要编程语言的活跃开发者数量进行了最新的估计。其中,JavaSc…

java编程中的持有对方引用是什么意思?有什么作用?

持有对方引用比如 A类有: h、j、 k, 3种方法,B类有 h 1种方法,但我B类必须要用到 A 类的 k方法怎么办呢?那么下面就要引入引用了!我在 B 类中创建个A类的引用,例如 如果在同一包下就不用导包了 …

Java学习之数据库中的范式和反范式

范式是关系数据库理论的基础,也是我们在设计数据库结构过程中所要遵循的规则和指导方法。数据库的设计范式是数据库设计所需要满足的规范。只有理解数据库的设计范式,才能设计出高效率、优雅的数据库,否则可能会设计出错误的数据库。 反范式&…

Java开发中如何用JDBC连接起数据库?

JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口&…

Java程序员最值得学习的10大技术

程序员们是最具有挑战性的工作,我们最大的挑战就是保持自己了解新的技术。技术变化很快,你大概每两年就会看到一个新版本的编程语言和框架。 就拿2017年来说,AR、VR、区块链、人工智能等等已经扑面而来了。除了这些离我们较远的,说几个离我们…

Java开发环境搭建详细步骤

安装JDK 首先下载jdk进官网下载jdk 选择相应的系统。 一键安装即可。 安装后会有两个jre,一个jdk内部的,和jdk外部的 关于两套jre的解释 一个jre文件在JDK的外部,一个jre文件在JDK内部,这就是所谓的专用JRE和公用JRE 专用 J…

这样学习JAVA 基础才能又快又稳!

微服务架构 企业和服务提供商正在寻找更好的方法将应用程序部署在云环境中,微服务被认为是未 来的方向。通过将应用和服务分解成更小的、松散耦合的组件,它们可以更加容易升级和扩展。 目前, 越来越流行的微服务技术是需要大家重视的。Spri…

Java设计模式之外观模式

外观模式是为了解决类与类之家的依赖关系的,像spring一样,可以将类和类之间的关系配置到配置文件中,而外观模式就是将他们的关系放在一个Facade类中,降低了类类之间的耦合度。 外观模式提供了一个统一的接口,用来访问…

Java编程初学者看什么书籍比较合适?

每次看到网上有很多想学习Java技术的小伙伴都在寻找一些书籍和视频,作为一个初学者,Java编程初学者 看什么样的书籍比较适合较适合呢?或者说,如何才能轻松入门又能深入学习呢?当然了这不是一本书就能解决的事。小编分享一下《Java核心技术》…

java开发,年薪15W的和年薪50W的差距

在这个IT系统动辄就是上亿流量的时代,Java作为大数据时代应用最广泛的语言,诞生了一批又一批的新技术,包括HBase、Hadoop、MQ、Netty、SpringCloud等等 。 一些独角兽公司以及腾讯、阿里、百度、网易等知名大厂对Java人才的需求量连年升级&a…

主流java框架理解

小编说一下分布式管理事务 简单的来说呢,分布式事务其实就是一次大的操作分成了好的小的操作,这些小的操作分别部署在不同的服务器上,而且属于不同的应用,分布式的事务其实就是保证这些小的事务要么都成功,要么都失败…

java中的多线程的示例

在讨论多线程之前,让我们先讨论线程。线程是进程中轻量级的最小部分,可以与同一进程的其他部分(其他线程)并发运行。线程是独立的,因为它们都有独立的执行路径,这就是为什么如果一个线程中发生异常,它不会影响其他线程…

几个改变世界的java工具

Java的开源生态系统强大而健康,这是我们创建OSCON Java的主要原因之一。在过去的十年中,有几个项目已经超越了简单的采用,并在Java世界中发挥了主导作用,进入了软件开发的一般领域,有些甚至深入到用户的日常生活中。 …

java网络篇-tcp的握手和挥手!

package com.wql.test; public class Test6 { public static void main(String[] args) { test1(ErrorType.ERROR_2); } public static void test1(ErrorType type){ switch(type){ case ERROR_1: System.out.println("参数type"type",value"type.…

java enum枚举使用例子

package com.wql.test; public class Test6 { public static void main(String[] args) { test1(ErrorType.ERROR_2); } public static void test1(ErrorType type){ switch(type){ case ERROR_1: System.out.println("参数type"type",value"type.…

经典Java编程面试题分析

求职者参加企业面试是进入职场的一个必经阶段,企业的面试官一般都用哪些问题来考求职者呢?不少求职人员认为,面试官提题出的问都是随机的,没有一定的规律。其实面试官基于想多方面了解面试者的情况下,会通过特定的提问…

Java高级架构师需要掌握什么?

没有谁能够随随便便的就成功,也没有谁能够随随便便就成为一名高级Java架构师。在Java行业中,如果按照排序,Java架构师一定是排在最顶端的,所以成为高级Java架构师,也是得有高超的Java技能,并且还得有着一定…

Java的三种工厂模式

一、简单工厂模式 简单工厂的定义:提供一个创建对象实例的功能,而无须关心其具体实现。被创建实例的类型可以是接口、抽象类,也可以是具体的类 实现汽车接口 public interface Car { String getName(); } 奔驰类 public class Benz im…