当前位置: 首页 > 编程日记 > 正文

Effective Java:对于全部对象都通用的方法

前言:

  读这本书第1条规则的时候就感觉到这是一本非常好的书。可以把我们的Java功底提升一个档次,我还是比較推荐的。这里我主要就关于覆盖equals、hashCode和toString方法来做一个笔记总结。希望可以与君共勉。


概述:

  这一章主要是说明一些对于全部对象都通用的方法。我们知道Java的多态是其特色之中的一个,而多态的体现方式中就有一种方式叫做“重写”。这些概念性的东西我想在大学我们学习Java的初期。老师就会如数家珍一样地灌输给我们。只是。在那个时候有多少人真的了解了什么是重载,什么是重写,什么是多态呢?

  而对于如今的一些开发人员而言,了解并使用它们是家常便饭。理所应当。可是。你真的是已经够了解吗?


相关内容:


1.覆盖equals时请遵守通用约定

  我们知道Java中假设须要比較两个对象是否相等的时候,就会用到equals。对于刚開始学习的人。可能遇到很多其它的是equals与"=="的差别,可能一開始大家都是一头雾水,傻傻分不清楚。这可能是由于你还没有地址和值的概念。

关于equals与"=="的差别,大家能够看看这篇博客——Java中equals和==的差别

  假设你还不是非常清楚equals和"=="的差别,那么,你能够花几分钟看看上面的博客,以便你能够明确。我们为什么要覆盖equals方法。

假设你已完全了解,那么便没有什么东西能够阻止你继续往下看。


  我们知道equals要实现的是逻辑上的等。站在数学的角度来看。两个事物相等的条件,有例如以下几个:

  1.自反性:对于不论什么非null的引用值x,x.equals(x)必须返回true.

  2.对称性:对于非空的引用值x,y,当且仅当x.equals(y)返回true时,y.equals(x)必须返回true.

  3.传递性:对于不论什么非null的引用值x,y,z,假设x.equals(y)=true。y.equals(z)=true,那么x.equals(z)也必须返回true。

  4.一致性:对于不论什么非null的引用值x,y。仅仅要equals的比較操作在对象中所用的信息没有被改动,多次调用x.equals(y)就会一致地返回true。或一致地返回false.

  5.对于非null的引用值x,x.equals(null)必须返回false.

  看完上面的这些数学式的规则,你是不是有一种哪要这么麻烦的事的感觉呢?从直观上来说,上面的这些规则的确是有一些麻烦,但你却不能忽视它们,不然麻烦的可就是你了。


  以下我会通过一些实例的学习,来说明这些规则。

  1.自反性:

<span style="font-family:Courier New;font-size:18px;">public static void equalsOppositeSelf() {String s = "ABC";Object o = new Object();System.out.println(s.equals(s));</span>
结果:
<span style="font-family:Courier New;font-size:18px;">true
true</span>


  2.对称性:

  对于对称性,可能你会感觉理所当然。

这是由于在你看来,我们要比較的两者必然是同一类型,这个必然太过理想化了。假设我们比較的两个对象不是同一种类型呢?以下能够看看这个样例。

<span style="font-family:Courier New;font-size:18px;">public final class CaseInsensitiveString {private final String s;public CaseInsensitiveString(String s) {if (s == null) {throw new NullPointerException();}this.s = s;}public boolean equals(Object o) {if (o instanceof CaseInsensitiveString) {return s.equalsIgnoreCase(((CaseInsensitiveString)o).s);}if (o instanceof String) {return s.equalsIgnoreCase((String)o);}return false;}
}</span>
  上面equals方法的代码实现了忽略大写和小写来比較字符串。

我们先不考虑同类型的两个对象比較,对于不同类型的两个对象,从上面的代码中我们能够看出。假设被比較的对象是一个String类型的。那么我们就能够去忽视大写和小写进行比較。答案也是在情理之中。以下看看例证:

  比較方法:

<span style="font-family:Courier New;font-size:18px;">public static void equalsSymmetric() {CaseInsensitiveString s1 = new CaseInsensitiveString("abc");String s2 = "abc";System.out.println("s1 == s2 ?

" + s1.equals(s2)); System.out.println("s2 == s1 ?

" + s2.equals(s1)); }</span>

  比較结果:

<span style="font-family:Courier New;font-size:18px;">s1 == s2 ? true
s2 == s1 ? false</span>

  这是为什么?不是说equals要满足对称性的吗?怎么这里又行不通了呢?

  细致推敲一番就能够发现了,我们在进行s1.equals(s2)的时候,是由于s1是CaseInsensitiveString类型的,它会运行到上面的代码,而s2是String类型的。s2.equals(s1)的比較自然是String中的equals方法。


  那你又会问。既然这样我们总不能去改动String类中的代码吧。假设你这样想,那我就无言以对了。我们知道一件事,两个不同类型的对象我就让它不同样去吧。也就是说,我们要有一个推断告诉程序。假设被比較的对象不是CaseInsensitiveString类型,那我们就不用客气直接返回false即可了。改动后的代码例如以下:

<span style="font-family:Courier New;font-size:18px;">    public boolean equals(Object o) {return o instanceof CaseInsensitiveString && ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);}</span>

  3.传递性

  传递性的推断是x = y, y = z。那么就能够推断x = z了。

  如今如果我们有一个类Point和一个Point的子类ColorPoint分别例如以下:

  Point

<span style="font-family:Courier New;font-size:18px;">public class Point {private final int x;private final int y;public Point(int x, int y) {this.x = x;this.y = y;}public boolean equals(Object o) {if (!(o instanceof Point)) {return false;}Point p = (Point) o;return p.x == x && p.y == y;}
}</span>


  ColorPoint

<span style="font-family:Courier New;font-size:18px;">public class ColorPoint extends Point {private final Color color;public ColorPoint(int x, int y, Color color) {super(x, y);this.color = color;}    
}</span>

  能够看到ColorPoint继承于Point,只是比Point类多一个颜色属性。当我们把ColorPoint与Point和Point与ColorPoint进行比較,例如以下:
<span style="font-family:Courier New;font-size:18px;">public static void equalsTransitivity() {Point p1 = new Point(1, 2);ColorPoint cp1 = new ColorPoint(1, 2, Color.BLACK);System.out.println("p1 == cp1 ?

" + p1.equals(cp1)); System.out.println("cp1 == p1 ? " + cp1.equals(p1)); }</span>

会得到例如以下结果:


<span style="font-family:Courier New;font-size:18px;">p1 == cp1 ?

true cp1 == p1 ? true</span>


  为什么两个都true呢?明明两个不同类型啊。假设真的要去考虑父类与子类的关系,也应该是一个true一个false啊。由于这里我们的ColorPoint本身没有重写Point的equals,它使用的是Point的equals,这时不管哪一次的比較中。都是去比較x和y,与color无关。

  这样就会导致一个问题,假设我的两个比較对象都是ColorPoint呢?这样一来假设我的两个ColorPoint的x和y全都一样,仅仅是color不同,那么不管怎么比較,其结果值都会是true.这里不会去检查color。

那你可能就会说,那我们就重写ColorPoint的equals啊。


  这里我们使用一条建议:复合优于继承(这一点在设计模式中也有体现)。

  实例示范:

<span style="font-family:Courier New;font-size:18px;">public static void equalsTransitivity() {Point p1 = new Point(1, 2);ColorPoint cp1 = new ColorPoint(1, 2, Color.BLACK);ColorPoint cp2 = new ColorPoint(1, 2, Color.BLUE);ColorPointNew cpn1 = new ColorPointNew(1, 2, Color.BLACK);ColorPointNew cpn2 = new ColorPointNew(1, 2, Color.BLUE);System.out.println("p1 == cp1 ? " + p1.equals(cp1));System.out.println("cp1 == p1 ?

" + cp1.equals(p1)); System.out.println("cp1 == cp2 ? " + cp1.equals(cp2)); System.out.println("cpn1 == cpn2 ? " + cpn1.equals(cpn2)); System.out.println("cpn1 == cp1 ? " + cpn1.equals(cp1)); System.out.println("cp1 == cpn1 ?

" + cp1.equals(cpn1)); }</span>


  结果:

<span style="font-family:Courier New;font-size:18px;">p1 == cp1 ? true
cp1 == p1 ?

true cp1 == cp2 ? true cpn1 == cpn2 ? false cpn1 == cp1 ? false cp1 == cpn1 ? false</span>

  上面的代码看上去非常简洁。


  4.一致性

  一致性的要求是,假设两个对象相等,它们就必须始终保持相等,除非它们中有一个对象被改动了。 


2.覆盖equals时总要覆盖hashCode

  为什么要说覆盖equals时总要覆盖hashCode呢?前面我们说的那些不都好好的么?一些equals必需的数学规则不是都已经满足了么?我们不是已经做得差点儿相同了么?是的。的确是差点儿相同了。只是我们还是要去覆盖hashCode方法。

这是由于我们假设把我们的对象与HashMap之类的Hash值联系起来,有此时候可能会感到困惑,甚至大失所望。

以下,我们就来列举一个样例,依据样例来说明再合适只是了。

  我们有这样一个PhoneNumber类:

package com.java.effective.samples;public final class PhoneNumber {private final short areaCode;private final short prefix;private final short lineNumber;public PhoneNumber(int areaCode, int prefix, int lineNumber) {rangeCheck(areaCode, 999,  "area code");rangeCheck(prefix, 999,  "prefix");rangeCheck(lineNumber, 9999,  "line number");this.areaCode = (short)areaCode;this.prefix = (short)prefix;this.lineNumber = (short)lineNumber;}private static void rangeCheck(int arg, int max, String name) {if (arg < 0 || arg > max) {throw new IllegalArgumentException(name + ": " + arg);}}@Overridepublic boolean equals(Object o) {if (o == this) {return true;}if (!(o instanceof PhoneNumber)) {return false;}PhoneNumber pNumber = (PhoneNumber)o;return (pNumber.lineNumber == lineNumber) && (pNumber.prefix == prefix) && (pNumber.areaCode == areaCode);}
}
上面的代码对equals的处理全然OK。只是假设我们把PhoneNumber和HashMap放在一起使用,结果会怎样?以下是我们的測试用例:

public static void hashCodePhoneNumber() {Map<PhoneNumber, String> map = new HashMap<PhoneNumber, String>();PhoneNumber phoneNumber = new PhoneNumber(707, 867, 9876);map.put(phoneNumber, "Jenny");System.out.println(map.get(new PhoneNumber(707, 867, 9876)));System.out.println(map.get(phoneNumber));}


结果:

null
Jenny


  我们能够这样来理解上面的map.put()。假设我们不去覆盖hashCode,那么当我们使用map.put时。我们是把这些PhoneNumber对象放在各个不同的盒子里。而我们去map.get()的时候。仅仅是去某一个盒子里去找(当然,假设map.get()和map.put()中的对象是同一个的话,当然能够找到)。

  而假设我们覆盖了hashCode方法。这时,假设通过hashCode计算出来的值是相等的,就会放在同一个盒子里。这样。仅仅要我们对象中保存的值是全然一致的,就会找到这个key所相应的value。

不知道你发现没有,这个hashCode有点类似于分类,这样在数据量比較大的情况下就会大大提高效率。

  我们能够通过下面两种方法来覆盖hashCode方法:

方法一:

@Overridepublic int hashCode() {return 42;}

方法二:

@Overridepublic int hashCode() {int result = 17;result = 31 * result + areaCode;result = 31 * result + prefix;result = 31 * result + lineNumber;return result;}

  首先两种方法都能够。通过上面的分析,从效率的角度来考虑,当然是另外一种方法更为恰当。

  所以在覆盖了equlas的同一时候,别忘了去覆盖hashCode.


3.始终要覆盖toString

  承上,就拿PhoneNumber类来说,假设我们不去覆盖类的toString()方法,后果就是当我们须要去打印这个类的对象时。会有一些并不是是我们想要的那种。类似这种:com.java.effective.samples.PhoneNumber@12a7e3

  有时我们不希望打印出这种对象,那我们就要去覆盖它们的toString方法了。

在这种方法里。我们能够依照我们自己的意愿来给类加入toString方法。对于PhoneNumber。我们能够这样来写:

@Overridepublic String toString() {String result = "";result += (areaCode + "-");result += (prefix + "-");result += (lineNumber);return result;}


打印结果:

707-867-9876

总结:

  在我们优化代码的时候最好还是考虑一下去合理地覆盖这些方法。能够让我们的代码更加健壮。


转载于:https://www.cnblogs.com/gccbuaa/p/6878875.html

相关文章:

HashSet中的add()方法( 一 )(详尽版)

让我们用例子来理解add()方法的底层代码吧&#xff0c;Let’s go&#xff1a; import java.util.HashSet;public class Test {public static void main(String[] args) {HashSet<String> names new HashSet<String>();names.add("Jim");//向HashMap集合…

Ansible03-管理变量、加密、事实

目录 一、管理变量 1.1、变量的基本用法 1.2、使用已注册变量捕获命令输出 二、管理加密 2.1、ansible-vault常用场景 三、管理事实 3.1、事实基本用法 3.2、创建自定义事实 3.3、魔法变量hostvars、group_names、groups、inventory_hostname 一、管理变量 1.1、变量…

HashSet中的add()方法( 零 )(详尽版)

我们知道在使用HashSet集合时&#xff0c;也就是在用HashMap集合&#xff0c;这是因为HashSet的底层是HashMap&#xff0c; public HashSet() {map new HashMap<>(); }在详述HashSet中的add()方法之前&#xff0c;我们要知道HashMap中的hash&#xff0c;因为在add()的底…

layui上传图片接口

mvc中 前台调用接口 url&#xff1a;"../upload/uploadfiles/" 然后开始接口 代码&#xff1a; string a ""; try { HttpFileCollection file context.Request.Files;//获取选中的文件 for (int i 0; i < file.Count; i) { string cFileName Path.G…

shell与 .sh文件与 .bash文件

一、shell和bash shell是LInux系统下的解释器&#xff0c;类似于windows下的cmd。shell对用户输入到窗口中的命令行进行解释&#xff0c;输入到内核。 bash同样是Linux系统下的解释器&#xff0c;是bash的改进版。 二、.sh文件与.bash文件 .sh文件和.bash文件都是脚本文件&a…

php session存入redis

一、 安装phpredis扩展php连接redis需要安装phpredis扩展。下载地址&#xff1a;https://github.com/phpredis/phpredis/releases&#xff0c;选用相应版本。笔者用的是php5.6.29&#xff0c;下载了phpredis-3.0.0安装出了问题&#xff0c;于是换成phpredis-2.2.8&#xff0c;正…

Ansible04-任务控制

目录 一、循环 二、条件 三、handlers 四、失败的处理 一、循环 使用 loop 关键字对一组项目迭代任务&#xff0c;循环变量 item 保存每个迭代过程中使用的值。 [studentworkstation ansible]$ vim loop.yml --- - name: Test loophosts: devgather_facts: novars:num:- …

HashSet中的add()方法( 三 )(详尽版)

上接HashSet中的add()方法( 二 )&#xff08;详尽版&#xff09; &#xff0c;前两篇说的是泛型为String类的add()方法的具体执行过程&#xff0c;此后三篇说说泛型为自定义类的add()方法的具体执行过程&#xff1a; 首先让我们来自定义一个学生类&#xff1a; public class …

mono修改配置

当前mono安装目录为:/home/mono&#xff0c;安装成功后修改配置需进入这个路径&#xff1a; cd /home/mono 1.修改TcpBinaryFrameManager.cs文件 cd /home/mono/mono-2.10.8 vim mcs/class/System.ServiceModel/System.ServiceModel.Channels.NetTcp/TcpBinaryFrameManager.cs …

[Java in NetBeans] Lesson 01. Java Programming Basics

这个课程的参考视频在youtube。 主要学到的知识点有&#xff1a; Create new project, choose Java Application.one .jar file/ package(.jar name with the same as package), one package can contains mutiple .java files.Comment mutiple lines by using "/* */&quo…

ubuntu中常用指令

常用指令 清空命令行 CtrlL Conda 创建虚拟环境 conda create -n 虚拟环境名称 python3.7.10 查看虚拟环境列表 conda info --envs 激活虚拟环境 conda activate 虚拟环境名 退出虚拟环境 conda deactivate 虚拟环境名称 安装功能包 conda install 功能包名称 卸载功能包 con…

Ansible05-部署文件

目录 一、部署文件的常用模块 二、使用jinja2文件部署自定义文件 一、部署文件的常用模块 部署文件常用模块有 file 创建、删除文件或目录&#xff0c;修改selinux上下文。copy 复制文件到受控节点上&#xff0c;也可以直接在受控结点上创建文件。fetch 从受控结点获取文件…

HashSet中的add()方法( 四 )(详尽版)

上接 HashSet中的add()方法( 三 )&#xff08;详尽版&#xff09; &#xff0c;我们重写一下Student类中的hashCode()方法来看看是否还能不能添加重复的学号了&#xff0c; 在学生类中重写hashCode()方法&#xff1a; public class Student {private String id;public Studen…

Laravel框架中的event事件操作

有时候当我们单纯的看 Laravel 手册的时候会有一些疑惑&#xff0c;比如说系统服务下的授权和事件&#xff0c;这些功能服务的应用场景是什么&#xff0c;其实如果没有经历过一定的开发经验有这些疑惑是很正常的事情&#xff0c;但是当我们在工作中多加思考会发现有时候这些服务…

yolact_ros出坑记录

教程&#xff1a;https://github.com/Eruvae/yolact_ros 下载通信中的话题msg 创建虚拟环境 conda create -n yolact python3.7.10 conda activate yolact 配置yolact环境 https://github.com/dbolya/yolact 运行效果如下所示&#xff1a; 在虚拟环境中安装需要的包 conda …

堡垒机高危命令正则表达式

堡垒机可以设置高危命令阻断&#xff0c;防止操作人员误操作造成删库跑路。但是什么是高危命令&#xff0c;需要管理员通过配置正则表达式&#xff0c;进行命令匹配。 今天2021年8月6日先匹配最常见的删库跑路命令 rm -rf / &#xff0c;以下几种表达式联合起来就可以阻断这类…

实验吧 速度爆破

题目链接&#xff1a;http://ctf5.shiyanbar.com/ppc/sd.php 这道题很简单呀&#xff0c;一点斜的歪的都没有&#xff0c;只是我一个小菜鸟在完成时会遇到一些小麻烦&#xff0c;几度想去看wp&#xff0c;最后还是忍住了。。。 思路很简单 1、 先抓取页面里生成的hash值 2、 循…

返回一个二维整数数组中的最大的子数组和

一。题目&#xff1a; 1、输入一个二维整形数组&#xff0c;数组里有正数有负数。 2、二维数组中连续的一个子矩阵组成一个子数组。 3、求所有子数组的和的最大值。 二.设计思想&#xff1a; .定义一个二维数组&#xff0c;使用二重循环对其进行赋值&#xff0c;对其进行遍历&a…

HashSet中的add()方法( 五 )(详尽版)

上接 HashSet中的add()方法( 四 )&#xff08;详尽版&#xff09; &#xff0c;我们再重写一下equals()方法来看看是否可以不能存入相同的id&#xff1a; 在学生类中再重写equals()方法&#xff1a; public class Student {private String id;public Student(String id) {thi…

ROS话题通信中创建自定义数据类型的两种方式

一、在同一个功能包下创建.msg文件 1、在功能包目录下创建msg文件夹 2、在文件夹中创建.msg文件 3、在该功能包的package.xml文件中加入&#xff1a; <build_depend>message_generation</build_depend><exec_depend>message_runtime</exec_depend>4、在…

2542513 - AS Java fails to start - Initialization of destination UMEBackendConnection failed

今天发现SAP As Java 开发机无法启动&#xff0c;根据 Note 2542513 处理该问题并记录。 今天发现SAP EPBPM服务器不能正常启动&#xff0c;jstart.exe进程显示 some process running 的黄灯状态。重启也不管用。到SAP官网 https://wiki.scn.sap.com/wiki/display/ASJAVA/%28J…

WordCount扩展与优化

合作者&#xff1a;201631062327&#xff0c;201631062128码云地址&#xff1a;https://gitee.com/LIUJIA6/WordCount3 一&#xff1a;项目说明 本次项目是在上次作业WorldCount的基础上&#xff0c;利用结对编程的思想&#xff0c;完成对WorldCount项目的功能扩展 -s 递归处理…

进制转换 位运算(包括补码、原码、反码、~0等一些零碎东西一次说清)

我发现网上关于标题上的内容介绍的都很零碎&#xff0c;因此为了方便查找、也为了本人对这一部分的充分理解&#xff0c;就想着写一篇这样的博客&#xff08;我分成了几个部分&#xff0c;以便查找&#xff09;&#xff1a; 一、进制转换 让我们先来看看各个进制的定义&#…

第三方物流是什么

​​第三方物流是指生产经营企业为集中精力搞好主业&#xff0c;把原来属于自己处理的物流活动&#xff0c;以合同方式委托给专业物流服务企业&#xff0c;同时通过信息系统与物流企业保持密切联系&#xff0c;以达到对物流全程管理的控制的一种物流运作与管理方式。 第三方物流…

ROS中cv_bridge如何用python3进行编译

最近遇到了个问题&#xff0c;cv_bridge实现了opencv和ros中图像数据类型的转换&#xff0c;但ros-melodic默认python版本是python2。在配置yolact环境的时候&#xff0c;要求是python3。 这就导致在ros自带的cv_bridge是python2版本&#xff0c;想使用python3的话需要自己去编…

Ansible06-管理roles

目录 一、roles的结构 二、RHEL系统roles 三、创建角色 四、部署角色 随着 playbook 的增多&#xff0c;有很多代码重用的机会。ansible 提供了一种方式——roles&#xff0c;只需将 roles 从一个项目复制到另一个项目&#xff0c;然后用 play 调用即可实现代码重用。这里的…

用Java中的HashSet写一个学生管理系统(添加、删除、修改、查询学生信息)

不废话&#xff0c;上代码&#xff1a; 先创建一个Student类&#xff1a; public class Student {private String id;private String name;private String mobile;private String address;public String getId() {return id;}public void setId(String id) {this.id id;}pub…

[BZOJ 2054]疯狂的馒头(并查集)

Description CQF十分喜欢吃馒头。兴奋之下他一下子买了N 个馒头请所有认识他的人吃。 但是CQF不喜欢白色&#xff0c;喜欢红色、黄色、绿色等鲜艳的颜色。于是他把所有白色的馒头排成一列。然后进行M 次染色操作。每个染色操作都是用一个神奇的刷子把连续的多个馒头染成特定的某…

RealSenseD435与ORB-SLAM2实现稠密建图

一、RealSenseD435介绍 RealSenseD435是一款结构光相机&#xff0c;使用左右目相机和红外光实现测距。有效测距范围为0.2~10m 二、ORBSLAM2_with_pointcloud_map的安装 git clone https://github.com/gaoxiang12/ORBSLAM2_with_pointcloud_map.gitclone代码&#xff0c;无法…

使用appium做自动化测试时,send_keyss只能输入字母数字,无法输入中文

解决方案&#xff1a; driver中增加以下2行配置&#xff1a; "unicodeKeyboard":True, #unicode编码输入 "resetKeyboard":True #隐藏软键盘 转载于:https://www.cnblogs.com/Inbreeze/p/9828568.html