为什么要关心均值和哈希码

等于和哈希码是每个Java对象的基本元素。 它们的正确性和性能对于您的应用程序至关重要。 但是,我们经常看到甚至有经验的程序员也忽略了类开发的这一部分。 在本文中,我将介绍一些与这两种非常基本的方法有关的常见错误和问题。

合同

提到的方法至关重要的是所谓的“合同”。 有大约的hashCode三个规则和五个约等于 (你可以找到他们在Java文档的Object类),但我们将讨论三个重要的。 让我们从hashCode()开始:

“只要在执行Java应用程序期间在同一个对象上多次调用它, hashCode方法就必须始终返回相同的整数,前提 是不修改该对象的equals比较中使用的 信息 。”
这意味着对象的哈希码不必是不变的。 因此,让我们看一下真正简单的Java对象的代码:

public class Customer {private UUID id;private String email;public UUID getId() {return id;}public void setId(final UUID id) {this.id = id;}public String getEmail() {return email;}public void setEmail(final String email) {this.email = email;}@Overridepublic boolean equals(final Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;final Customer customer = (Customer) o;return Objects.equals(id, customer.id) &&Objects.equals(email, customer.email);}@Overridepublic int hashCode() {return Objects.hash(id, email);}
}

您可能已经注意到, equalshashCode是由我们的IDE自动生成的。 我们确信这些方法不是一成不变的,并且肯定会广泛使用此类。 也许这样的类太常见了,这样的实现没有错吗? 因此,让我们看一个简单的用法示例:

def "should find cart for given customer after correcting email address"() {given:Cart sampleCart = new Cart()Customer sampleCustomer = new Customer()sampleCustomer.setId(UUID.randomUUID())sampleCustomer.setEmail("emaill@customer.com")HashMap customerToCart = new HashMap<>()when:customerToCart.put(sampleCustomer, sampleCart)then:customerToCart.get(sampleCustomer) == sampleCartand:sampleCustomer.setEmail("email@customer.com")customerToCart.get(sampleCustomer) == sampleCart
}

在上述测试中,我们希望确保在更改示例客户的电子邮件后,我们仍然能够找到其购物车。 不幸的是,该测试失败。 为什么? 因为HashMap将密钥存储在“存储桶”中。 每个存储桶都具有特定范围的哈希。 由于这个想法,哈希映射非常快。 但是,如果我们将密钥存储在第一个存储桶中(负责1到10之间的哈希),然后hashCode方法的值返回11而不是5(因为它是可变的),会发生什么? 哈希图尝试查找密钥,但它检查第二个存储桶(持有哈希11到20)。 它是空的。 因此,对于给定的客户根本没有购物车。 这就是为什么拥有不可更改的哈希码如此重要的原因!

实现它的最简单方法是使用不可变对象。 如果由于某种原因在您的实现中不可能,那么请记住将hashCode方法限制为仅使用对象的不可变元素。
第二个hashCode规则告诉我们,如果两个对象相等(根据equals方法),则哈希值必须相同。 这意味着我必须将这两种方法关联起来,而这可以通过基于相同的信息(基本上是字段)来实现。

最后但并非最不重要的一点是,它告诉我们有关相等的传递性。 它看起来微不足道,但事实并非如此-至少在您考虑继承时。 假设我们有一个扩展了日期时间对象的日期对象。 为一个日期实现equals方法很容易–当两个日期相同时,我们返回true。 日期时间也一样。 但是,当我想将日期与日期时间进行比较时会发生什么? 他们有相同的日期,月份和年份是否足够? 是否可以比较小时和分钟,因为日期上没有此信息? 如果我们决定使用这种方法,那我们就搞砸了。 请分析以下示例:

2016-11-28 == 2016-11-28 12:202016-11-28 == 2016-11-28 15:52

由于equals的传递性,我们可以说2016-11-28 12:20等于2016-11-28 15:52这当然是愚蠢的。 但是,当您考虑平等合同时是正确的。

JPA用例

不让我们谈论JPA。 看起来在这里实现equals和hashCode方法非常简单。 每个实体都有唯一的主键,因此基于此信息的实现是正确的。 但是,何时分配了此唯一ID? 在对象创建期间还是在刷新更改数据库之后? 如果您是手动分配ID,则可以,但是如果您依赖底层引擎,则可能会陷入陷阱。 想象这样的情况:

public class Customer {@OneToMany(cascade = CascadeType.PERSIST)private Setaddresses = new HashSet<>();public void addAddress(Address newAddress) {addresses.add(newAddress);}public boolean containsAddress(Address address) {return addresses.contains(address);}
}

如果地址的hashCode基于ID,则在保存Customer实体之前,我们可以假定所有哈希码都等于零(因为还没有ID)。 刷新更改后,将分配ID,这也会导致新的哈希码值。 现在,您可以调用containsAddress方法,不幸的是,由于与第一部分有关HashMap的第一部分中所述的原因相同,该方法将始终返回false。 我们如何保护这种问题? 据我所知,有一种有效的解决方案– UUID。

class Address {@Id@GeneratedValueprivate Long id;private UUID uuid = UUID.randomUUID();// all other fields with getters and setters if you need@Overridepublic boolean equals(final Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;final Address address = (Address) o;return Objects.equals(uuid, address.uuid);}@Overridepublic int hashCode() {return Objects.hash(uuid);}
}

uuid字段(可以是UUID或简单地为String)是在对象创建期间分配的,并且在整个实体生命周期中保持不变。 它存储在数据库中,并在查询该对象后立即加载到字段中。 它或当然会增加一些开销和占用空间,但没有免费的东西。 如果您想了解有关UUID方法的更多信息,可以查看有关此内容的两篇精彩文章:

  • https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/
  • https://vladmihalcea.com/2014/07/01/hibernate-and-uuid-identifiers/

偏向锁定

十多年来,Java中的默认锁定实现使用一种称为“偏置锁定”的东西。 可以在标志注释中找到有关此技术的简要信息(来源: Java Tuning White Paper ):

-XX:+ UseBiasedLocking
启用一种用于提高无竞争同步性能的技术。 一个对象被“偏向”线程,该线程首先通过Monitorenter字节码或同步方法调用来获取其监视器。 在多处理器计算机上,该线程执行的后续与监视器相关的操作相对要快得多。 在启用了此标志的情况下,某些具有大量无竞争同步的应用程序可能会实现明显的加速。 尽管已尝试将负面影响降到最低,但某些具有某些锁定模式的应用程序可能会变慢。

对于我们而言,有关此帖子的有趣之处是内部如何实现偏置锁定。 Java使用对象标头存储持有锁的线程的ID。 问题在于对象标头的布局定义明确(如果您有兴趣,请参阅OpenJDK源hotspot / src / share / vm / oops / markOop.hpp ),并且不能像这样“扩展”它。 在64位中,JVM线程ID的长度为54位,因此我们必须决定是否要保留此ID或其他名称。 不幸的是,“其他”意味着对象哈希码(实际上是身份哈希码,存储在对象头中)。

每当您对自Object类以来没有覆盖它的任何对象调用hashCode()方法时,或者当您直接调用System.identityHashCode()方法时,都将使用此值。 这意味着当您检索任何对象的默认哈希码时; 您禁用对此对象的偏向锁定支持。 这很容易证明。 看一下这样的代码:

class BiasedHashCode {public static void main(String[] args) {Locker locker = new Locker();locker.lockMe();locker.hashCode();}static class Locker {synchronized void lockMe() {// do nothing}@Overridepublic int hashCode() {return 1;}}
}

当您使用以下VM标志运行main方法时: -XX:BiasedLockingStartupDelay=0 -XX:+TraceBiasedLocking您会看到……没有什么有趣的事情:)

但是,从Locker类中删除hashCode实现后,情况将发生变化。 现在我们可以在日志中找到以下行:
Revoking bias of object 0x000000076d2ca7e0 , mark 0x00007ff83800a805 , type BiasedHashCode$Locker , prototype header 0x0000000000000005 , allow rebias 0 , requesting thread 0x00007ff83800a800

为什么会发生? 因为我们要求提供身份哈希码。 总结一下这一部分:类中没有hashCode意味着没有偏向锁定。

非常感谢https://www.sitepoint.com/java/的 Nicolai Parlog审阅了这篇文章并指出了一些错误。

翻译自: https://www.javacodegeeks.com/2016/12/care-equals-hashcode.html

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

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

相关文章

python把英语句子成分字母_句子成分的表达字母

1.一【1】1、一般现在时(do); 2、一般过去时(did); 3、一般将来时( will do)(be going to do); 4、一般过去将来时( would do); 5、现在进行时( be doing); 6、过去进行时(was/were doing); 7、将来进行时( will be doing); 8、过去将来进行时( would be doing)(was/were going…

AntDesign Form表单字段校验的三种方式

1.使用getFieldDecorator的rules规则 最简单的方法就是使用getFieldDecorator中的rules验证。rules中定义校验规则&#xff0c;message为校验不通过时的提示文字。 {getFieldDecorator(inputContent, {rules: [{required: true, message: 请输入内容!,}],})(<Input /> )}…

matlab的max与min函数

max作用于矩阵有比较并保留较大数字的功能。如下所示&#xff1a; 矩阵a中每个元素与2相比较取较大值&#xff0c;1比2小&#xff0c;所以替换为2. 矩阵a中每个元素与3相比取较小值&#xff0c;4比3小&#xff0c;所以替换为3. a 1 2 3 4 K>> max(a,2)…

JAVA Servlet API简介及接口与类的用法

本章介绍了Java Servlet API中的主要的接口与类的用法&#xff0c;并且介绍了它们的生命周期。表4-1 对接口与类的作用与生命周期作了归纳。这些接口与类的生命周期都由Servlet容器来控制&#xff0c;容器会在特定的时刻创建或销毁它们的实例。 表4-1 Servlet API中的主要的接口…

Spring框架的事务管理的基本概念

1. 事务&#xff1a;指的是逻辑上一组操作&#xff0c;组成这个事务的各个执行单元&#xff0c;要么一起成功,要么一起失败&#xff01; 2. 事务的特性* 原子性* 一致性* 隔离性* 持久性3. 如果不考虑隔离性,引发安全性问题* 读问题:* 脏读:* 不可重复读:* 虚读:* 写问题:* 丢失…

python rest 框架_python-更新用户REST框架Django

我需要在REST框架中更新我的用户views.pyclass UserUpdate(APIView):permission_classes (permissions.IsAuthenticated,)def post(self,request):userUser.objects.get(idrequest.user.id)try:user_serializerUserSerializer(request.user,datarequest.data, partialTrue)if …

matlab将满足某一条件的矩阵元素置零

想把矩阵中小于100的元素置零&#xff0c;有两种方法&#xff1a; 方法一&#xff1a;S(S<100)0; 方法二&#xff1a;S(find(S<100))0; 原理&#xff1a; A [ 1 2; 3 4]; B (A>2) %找到大于2 的位置索引 B 0 0 1 1 %形成布尔矩阵 A(B)0 %将对…

linux nona怎么用_nano命令_Linux nano 命令用法详解:字符终端文本编辑器

nano是一个字符终端的文本编辑器&#xff0c;有点像DOS下的editor程序。它比vi/vim要简单得多&#xff0c;比较适合Linux初学者使用。某些Linux发行版的默认编辑器就是nano。nano命令可以打开指定文件进行编辑&#xff0c;默认情况下它会自动断行&#xff0c;即在一行中输入过长…

4g内存 堆内存分配多少_我需要多少内存

4g内存 堆内存分配多少什么是保留堆&#xff1f; 我需要多少内存&#xff1f; 在构建解决方案&#xff0c;创建数据结构或选择算法时&#xff0c;您可能会问自己&#xff08;或其他人&#xff09;这个问题。 如果此图包含1,000,000条边并且我使用HashMap进行存储&#xff0c;此…

centos下网络配置方法(网关、dns、ip地址配置)

centos网络配置实例 1&#xff0c;配置DNS vi /etc/resolv.conf 加入: 复制代码代码如下:nameserver 192.168.0.1 nameserver 8.8.8.8nameserver 8.8.4.42&#xff0c;配置网关&#xff1a; vi /etc/sysconfig/network 加入&#xff1a; GATEWAY192.168.0.1 完整的如下&#xf…

matlab的repmat函数

B repmat(A,m,n) 1.作用&#xff1a;将矩阵A的内容堆叠在mxn大小的矩阵B中 2.应用&#xff1a; Brepmat([1 2;3 4],3,2) B 1 2 1 2 3 4 3 4 1 2 1 2 3 4 3 4 1 2 1 2 3 4 …

Alias Method解决随机类型概率问题(别名算法)

举个例子&#xff0c;游戏中玩家推倒了一个boss&#xff0c;会按如下概率掉落物品&#xff1a;10%掉武器 20%掉饰品 30%掉戒指 40%掉披风。现在要给出下一个掉落的物品类型&#xff0c;或者说一个掉落的随机序列&#xff0c;要求符合上述概率。 一般人会想到的两种解法 第一种算…

centos mysql php tomcat_Linux 安装JDK Tomcat MySQL的教程(使用Mac远程访问)

一 环境阿里云服务器: CentOS 7.4 64位(基于RedHat)本机: macOS High Sierra二 压缩包三 文件传输输入SFTP命令连接 -> 输入实例登录密码sftp root公网IP上传put 本地文件 服务器路径下载get 服务器文件 本地路径四 远程访问输入SSH命令连接 ->输入实例登录密码ssh root公…

Java时间和日期指南

长期以来&#xff0c;正确处理日期&#xff0c;时间&#xff0c;时区&#xff0c;夏时制&#xff0c;and年等一直是我的烦恼。 本文并不是一个全面的指南时域&#xff0c;请参阅日期和时间在Java中 -更详细&#xff0c;但略有下降&#xff0c;ekhem&#xff0c;日期。 它仍然是…

Java项目打war包的方法

最近好忙好忙&#xff0c;整理下心情给大家分享下自己在工作中遇到的一点小技巧&#xff0c;希望给遇到同样麻烦的同学一点帮助。 我们知道Java项目打war包可以在Eclipse和MyEclipse工具中自动打包&#xff0c;就是右键&#xff0c;然后导出war包就可以了&#xff0c;可是我发现…

matlab 的cat函数

cat&#xff1a;用来联结数组 1、用法&#xff1a;C cat(dim, A, B) 按dim来联结A和B两个数组。 C cat(dim, A1, A2, A3, ...) 按dim联结所有输入的数组。 2、举例 acat(3,A,B) 左括号后的3表示构造出的矩阵维数&#xff1b;在新的矩阵中第1、2维就是A和B这两个矩…

charles抓取手机APP,配置正确却抓不到数据

1、确保电脑的防火墙是关闭状态 2、如果还是不行的话&#xff0c;把手机wifi断掉后重新连接 转载于:https://www.cnblogs.com/ding-daisy/p/10141843.html

composer查看当前镜像取消_国内全量镜像大全

# 国内全量镜像大全**配置文件.gitignore **json{"name": "topthink/think","description": "the new thinkphp framework","type": "project","keywords": ["framework","thinkphp&quo…

利用C语言创建和使用DLL文件

有感于讲C语言的DLL文件的文章很少&#xff0c;自己查了半天&#xff0c;写了这么个非常简单的教程。自己也是摸C语言不久&#xff0c;依然感觉处于编程苦手的阶段。1&#xff09;为什么使用DLL文件C语言复用代码有很多的形式&#xff0c;利用动态链接库&#xff08;DLL&#x…

vba判断文件编码格式_如何在VBA判断EXCEL或WORD文件已经打开,并用代码关闭

谢谢寻欢&#xff0c;原来核心就是GETOBJECT&#xff0c;特帖帮助内容&#xff0c;与大家分享&#xff1a;GetObject 函数示例该示例使用 GetObject 函数来获取对指定的 Microsoft Excel 的工作表 (MyXL) 的引用。它使用工作表的 Application 属性来显示或关闭 Microsoft Excel…