equals方法通用约定-阿里云开发者社区

开发者社区> 李子捌> 正文

equals方法通用约定

简介: equals方法通用约定
+关注继续查看
(福利推荐:你还在原价购买阿里云服务器?现在阿里云0.8折限时抢购活动来啦!4核8G企业云服务器仅998元/3年,立即抢购>>>:9i0i.cn/aliyun

福利推荐:阿里云、腾讯云、华为云等大品牌云产品全线2折优惠活动来袭,4核8G云服务器899元/3年,新老用户共享优惠,点击这里立即抢购>>>

1、简介

Java程序员都知道java.lang.Object类,这是所有类的超类。Object类中提供了几个public的方法,比如:

public boolean equals(Object var1) {

? ? ? ? return this == var1;

}


public String toString() {

? ? return this.getClass().getName() + "@" + Integer.toHexString(this.hashCode());

}

这些public方法第一是提供给所有的子类去扩展(覆盖),第二是明确了Java中的类所具备的通用约定。因此Java中的类在覆盖这些方法是,都需要遵守通用约定,避免程序员们各玩各的。

那具体应该怎么覆盖,又应该遵守那些通用约定呢?

其实这是一个非常复杂的问题,正如Java大师约书亚·布洛克(Joshua Bloch)所说:它看似简单,但是往往很多高级程序员也无法完全正确的实现,并且如果不严格遵守,往往会导致非常严重的后果。

image


2、正文

2.1 什么时候需要重写equals方法

总结一句话就是:当我们需要比较两个对象是否“逻辑相等”时,可能需要考虑重写equals方法,比如我们需要比较值类型的类Integer、String,这些类经常需要用于承载和比较值是否相等,或者用于做个Map、Set等集合的Key值,在这些场景下我们是需要严格的去重写equals方法的。(我这里说的是可能,是因为很多情况下无招胜有招,我们或许不需要重写equals方法,至于那些场景不需要重写equals方法这个会在后面说!)

2.2 什么时候不需要重写equals方法

不需要实现equals方法的场景非常多,我们大致的举例说明一下:

  • 类的每个实例唯一。比如说:枚举类型,枚举类型虽然也属于上面说的“值类”,但是由于枚举类的每个值只会存在一个对象,因此不需要重写equals方法
  • 类的访问权限是私有的(类私有、包级私有),并且确保equals方法不会被调用。说白了就是其他类无法调用到这个类的equals方法
  • 超类覆写的equals方法,在子类仍然适用。这种情况下我们就无需再多此一举了,在Java的JDK源码中,set、List、Map都直接使用了超类的equals方法,比如在HashSet提供的方法中,并未有equals方法的实现,这是因为其父类AbstractSet中覆写了equals方法,且父类实现的逻辑对于子类也是可用的。

image

  • 类本身无需提供“逻辑相等”的功能。这种情况其实非常常见,比如我们在实际开发中经常写的工具类,这些类的实例只是用来完成某些任务,并不需要比较它们是否逻辑相等。比如Java提供的java.util.regex.Pattern类,并未实现equals方法,因为它觉得没人会比较两个Pattern对象是否相等。


2.3 重写equals方法需要遵守哪些规则

重写equals方法有几条看起来很简单,但是实现起来几乎无法完全保证的约定:

  1. 自反性(Reflexivity):非null情况下,x.equals(x)必须为true
  2. 对称性(Symmetry):非null情况下,x.equals(y) = true则y.equals(x) = true
  3. 传递性(Transitive):非null情况下,x.equals(y) = true && y.equals(z) = true则x.equals(z) = true
  4. 一致性(Consistent):非null情况下,x.equals(y) = true只要x或y其中任意一个对象不被修改,那么x.equals(y) = true应该恒成立
  5. 非空性(Non-nullity):x不为null的情况下,x.equals(null)必须返回false

看到这五条规则是不是觉得头大,平时我们在写的时候,压根就没考虑过这么多条条框框,只有能实现功能上的逻辑相等了就行!

image

其实我觉得这么想也不能说是完全不对,因为如果一定要完完全全的按照它这个规范来,那么面向对象很多功能都用不了了,比如说继承。

其实Java的JDK中也是有些代码不满足上面说的这五条规范的,比如我们看下如下这段代码(猜猜它会输出什么?):

package com.lizba.tips;


import java.sql.Timestamp;

import java.util.Date;


/**

?* <p>

?* ? ?Java自带jdk equals方法的对称性测试

?* </p>

?*

?* @Author: Liziba

?* @Date: 2021/10/24 14:48

?*/

public class EqualsDemo {



? ? public static void main(String[] args) {

? ? ? ? Date date = new Date();

? ? ? ? Timestamp timestamp = new Timestamp(date.getTime());

? ? ? ? System.out.println("Date equals to Timestamp: " + date.equals(timestamp));

? ? ? ? System.out.println("Timestamp equals to Date: " + timestamp.equals(date));

? ? }


}

是的你没看错,第一个输出了true,第二个输出了false。很显然这不满足第二点:对称性(Symmetry)。

Date equals to Timestamp: true

Timestamp equals to Date: false

基于这种情况,Java并没有很好的办法去解决。只能说告诉你不用混用Date和Timestamp,并且无论如何不要去equals比较Date和Timestamp,这个在Timestamp中的类和equals方法上也是有说明的!

2.4 实现高质量equals方法的诀窍

上面聊了一些什么时候需要重写equals方法、什么时候不需要重写equals方法、重写equals方法需要遵守的规则。这里我们聊一聊实现高质量equals方法的诀窍。

在这里我将会引用java.util.AbstractSet类中的equals方法来阐述如何写一个高质量的equals方法,因为小捌发现它非常经典。

java.util.AbstractSet中的equals方法:

public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> {

?

? ? // ...

? ??

? ? public boolean equals(Object o) {

? ? ? ? if (o == this)

? ? ? ? ? ? return true;


? ? ? ? if (!(o instanceof Set))

? ? ? ? ? ? return false;

? ? ? ? Collection<?> c = (Collection<?>) o;

? ? ? ? if (c.size() != size())

? ? ? ? ? ? return false;

? ? ? ? try {

? ? ? ? ? ? return containsAll(c);

? ? ? ? } catch (ClassCastException unused) ? {

? ? ? ? ? ? return false;

? ? ? ? } catch (NullPointerException unused) {

? ? ? ? ? ? return false;

? ? ? ? }

? ? }

? ??

? ? // ...

? ??

}

第一点:o == this

使用==操作符,判断比较对象和当前对象的引用是否相等,如果相等代表同一个对象,那就直接返回true。

第二点:o instanceof Set

通过instanceof操作符检查参数类型是否正确,如果类型都不对就不需要比较了。

第三点:c.size() != size()

这是在Abstract中的特殊存在,并不是所有的都需要这样比较,提前比较大小的好处是无需进行每个域的比较,如果大小都不相等,就可以直接返回了。通常情况下,这样做性能更好!

第四点:containsAll(c)

对该类中的每一个域进行比较,如果所有的域都相等则返回true,如果不相等返回false。

第五点:重写hashcode

重写equals方法时一定要重写hashcode方法,比如java.util.AbstractSet中重写了hashCode()方法,它将每个域的hashcode进行了拼接

public int hashCode() {

? ? int h = 0;

? ? Iterator<E> i = iterator();

? ? while (i.hasNext()) {

? ? ? ? E obj = i.next();

? ? ? ? if (obj != null)

? ? ? ? ? ? h += obj.hashCode();

? ? }

? ? return h;

}

第六点:不要修改equals(Object o)的参数类型

这一点看似很简单,但是如果你不是用IDE自动生成的equals方法,而是自己手动敲得代码,很容易会将Object类型,改成当前类的类型,这种做法是不对的哈!因为这不是重写(Override),这是重载(Overload)

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
9473 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的,?mysql的 3306,?mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建. ? have?fun! ?将编程看作是一门艺术,而不单单是个技术。
10839 0
使用SSH远程登录阿里云ECS服务器
远程连接服务器以及配置环境
2460 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
13151 0
腾讯云服务器 设置ngxin + fastdfs +tomcat 开机自启动
在tomcat中新建一个可以启动的 .sh 脚本文件 /usr/local/tomcat7/bin/ export JAVA_HOME=/usr/local/java/jdk7 export PATH=$JAVA_HOME/bin/:$PATH export CLASSPATH=.
4619 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
6882 0
阿里云服务器ECS登录用户名是什么?系统不同默认账号也不同
阿里云服务器Windows系统默认用户名administrator,Linux镜像服务器用户名root
4002 0
+关注
李子捌
简介: CSDN优秀作者、华为云专家 领域: Java框架、并发编程、分布式、微服务、Redis、HarmonyOS、中间件等技术
240
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载


http://www.vxiaotou.com