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);
}
}
@Override
public 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方法:
方法一:
@Override
public int hashCode() {
return 42;
}
方法二:
@Override
public 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。我们能够这样来写:
@Override
public String toString() {
String result = "";
result += (areaCode + "-");
result += (prefix + "-");
result += (lineNumber); return result;
}
打印结果:
707-867-9876
总结:
在我们优化代码的时候最好还是考虑一下去合理地覆盖这些方法。能够让我们的代码更加健壮。
Effective Java:对于全部对象都通用的方法的更多相关文章
- Effective Java -- 对于所有对象都通用的方法
覆盖equb时请遵循通用约定: 自反性.对于任何非null的引用值x,xequals(x)必须返回true. 对称性.对于任何非null的引用值x和y,当且晋档y.equals(x)返回true的时候 ...
- [Effective Java]第三章 对所有对象都通用的方法
声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...
- [Effective Java 读书笔记] 第三章 对所有对象都通用的方法 第八 ---- 九条
这一章主要讲解Object类中的方法, Object类是所有类的父类,所以它的方法也称得上是所有对象都通用的方法 第八条 覆盖equals时需要遵守的约定 Object中的equals实现,就是直接对 ...
- Java高效编程之二【对所有对象都通用的方法】
对于所有对象都通用的方法,即Object类的所有非final方法(equals.hashCode.toString.clone和finalize)都有明确的通用约定,都是为了要被改写(override ...
- 《Effective Java》第3章 对于所有对象都通用的方法
第8条:覆盖equals时请遵守通用约定 覆盖equals方法看起来似乎很简单,但是有许多覆盖方式会导致错误,并且后果非常严重.最容易避免这类问题的办法就是不覆盖equals方法,在这种情况下,类的每 ...
- Effective Java读书笔记——第三章 对于全部对象都通用的方法
第8条:覆盖equals时请遵守通用的约定 设计Object类的目的就是用来覆盖的,它全部的非final方法都是用来被覆盖的(equals.hashcode.clone.finalize)都有通用约定 ...
- [Java读书笔记] Effective Java(Third Edition) 第 3 章 对于所有对象都通用的方法
第 10 条:覆盖equals时请遵守通用约定 在不覆盖equals方法下,类的每个实例都只与它自身相等. 类的每个实例本质上都是唯一的. 类不需要提供一个”逻辑相等(logical equality ...
- 《Effective Java》读书笔记 - 3.对于所有对象都通用的方法
Chapter 3 Methods Common to All Objects Item 8: Obey the general contract when overriding equals 以下几 ...
- 2.对于所有对象都通用的方法_EJ
第8条: 覆盖equals时请遵守通用约定 我们在覆盖equals方法时,必须遵守它的通用约定: 1.自反性.对于任何非null的引用值x,x.equals(x)必须返回true: 2.对称性.对于任 ...
随机推荐
- 简单认识http协议
1.什么是TCP/IP 如果要了解一个人,可以从他归属的集体聊起来.我们的HTTP协议就属于TCP/IP协议家族中的一员,了解HTTP协议再整个网络流程中的地位,也能更加充分的理解HTTP协议. 要 ...
- scrapy 简单操作
1.创建一个简单的scrapy项目 scrapy startproject search(项目名称)按照提示cd searchscrapy genspider serachname search.co ...
- python中的深拷贝和浅拷贝(面试题二)
一.浅拷贝 定义:浅拷贝只是对另外一个变量的内存地址的拷贝,这两个变量指向同一个内存地址的变量值. 浅拷贝的特点: 公用一个值: 这两个变量的内存地址一样: 对其中一个变量的值改变,另外一个变量的值也 ...
- Git系列学习(1)-Git安装
一.概述 msysGit名字前面的四个字面来源于MSYS项目: MSYS项目来源于MinGW(Minimalist GNU for Windows,最简GNU工具集) 通过添加一个bash提供的she ...
- Glide4.0 centerCrop属性和圆角 冲突
首先致谢:https://blog.csdn.net/flyinbed_/article/details/75506062 咱们不是代码的生产者,只是代码的搬运工. 最近有个工作中有个需求就是展示的图 ...
- JS——百度背景图
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- JS——input标签注册事件
注意:淘宝的lable是用定位制作的,事件是oninput事件 <!DOCTYPE html> <html> <head lang="en"> ...
- Python_多线程1(创建线程,简单线程同步)
threading 模块除了包含 _thread 模块中的所有方法外,还提供的其他方法: threading.currentThread(): 返回当前的线程变量. threading.enumera ...
- Vue项目优化首屏加载速度
Vue项目部署上线后经常会发现首屏加载的速度特别慢:那么有那写能做的简单优化呢 一.路由的懒加载 路由懒加载也就是 把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件. 结合 ...
- 写一个 sum方法,在使用下面任一语法调用时,都可以正常工作
console.log(sum(2,3)); // Outputs 5 console.log(sum(2)(3)); // Outputs 5 (至少)有两种方法可以做到: 方法1: functio ...