第8条:覆盖equals时请遵守通用约定

①约定的内容

  • 自反性。对于任何非null的引用值x。x.equals(x)必须返回true。
  • 对称性。对于任何非null的引用值x和y。当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。
  • 传递性。对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,则x.equals(z)也必须是true。
  • 一致性。对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致的返回true或者一致返回false。
  • 非空性。对于任何非null的引用值x,x.equals(null)必须返回false。

书中举了两个例子,主要是针对对称性和传递性。下面先看一下对称性的反例

public final class CaseInsensitiveString {
private final String s; public CaseInsensitiveString(String s) {
if (s == null)
throw new NullPointerException();
this.s = s;
}
@Override
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;
} public static void main(String[] args) {
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
System.out.println(cis.equals(s) + " " + s.equals(cis));
}
}

该例子人为封装了一个不区分大小写的字符串类。其中,equals方法传入普通字符串时,也是可以比较的。但是反过来,普通字符串调用equals方法传入CaseInsensitiveString类型的字符串,就返回false了(相信实际中没人会这么用,这里只是给大家提个醒)。

下面再看一个传递性的反例

点类,横纵坐标都相等即认为是相等

//点类
public class Point {
private final int x;
private final int y; public Point(int x, int y) {
this.x = x;
this.y = y;
} @Override
public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point) o;
return p.x == x && p.y == y;
} @Override
public int hashCode() {
return 31 * x + y;
}
}

带颜色的点类,横纵坐标都相等,且颜色也相同就认为是相等

public class ColorPoint extends Point {
private final Color color; public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
} @Override
public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
return super.equals(o) && ((ColorPoint) o).color == color;
} public static void main(String[] args) {
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
System.out.printf("%s %s %s%n", p2.equals(p1), p2.equals(p3),
p1.equals(p3));
}
} public enum Color {
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}

这时,p2.equals(p1)是true,p2.equals(p3)也是true,但是p1.equals(p3)是false,不符合传递性。归根结底的原因是p2是基类生成的对象,p1和p3是子类生成的对象。因此,p2和p1、p3比较时,就变色盲了。

②实现高质量equals方法的诀窍。

  • 首先使用“==”操作符检查传入的是否是这个对象的引用。
  • 然后使用Instanceof操作符检查“参数类型是否正确”。
  • 把Object强转为正确的类型再进行一系列比较。
  • 当编写完后,注意对称性和传递性。
  • 注意,一定要覆盖Object类中equals方法,不要重写成equals(MyClass o)。

第9条:覆盖equals时总要覆盖hashCode

首先看一个例子,它完全是根据第8条的诀窍写出来的。

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 pn = (PhoneNumber) o;
return pn.lineNumber == lineNumber && pn.prefix == prefix
&& pn.areaCode == areaCode;
} public static void main(String[] args) {
Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
m.put(new PhoneNumber(707, 867, 5309), "Jenny");
System.out.println(m.get(new PhoneNumber(707, 867, 5309)));
}
}

但是,返回结果却不是Jenny,而是null。这是由于PhoneNumber类没有覆盖hashCode方法,从而导致两个相等的实例具有不相等的散列码,违反了hashCode的约定,因此put方法把电话号码对象存放在一个散列桶中,get方法却在另一个散列桶中查找电话号码。即使凑巧放到一个桶中,get仍然返回null,因为HashMap有一项优化,如果散列码不匹配,直接不检验对象的等同性了。

修正问题的办法就是提供一个hashCode方法。例如

@Override
public int hashCode(){return 42;}

它确保了相同的对象总是有同样的散列码。但它也是极为恶劣的,因为它使得每一个对象都有同样的散列码。这样,所有的对象都会被放到同一个散列桶中,使得散列退化成了链表。理想情况下,散列函数应该把集合中不相等的实例均匀地分布到所有可能的散列值上。

对于不同的域(上述例子是3个short),有一套不同的计划散列码的规则。好在Eclipse已经可以帮我们自动生成了。

至于为什么要使用31这个数字,博文http://blog.csdn.net/mingli198611/article/details/10062791给出了分析

A.31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终的出来的结果只能被素数本身和被乘数还有1来整除!。(减少冲突)

B.31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化.(提高算法效率)

C.选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)

D.并且31只占用5bits,相乘造成数据溢出的概率较小。

Effective Java2读书笔记-对于所有对象都通用的方法(一)的更多相关文章

  1. Effective Java2读书笔记-对于所有对象都通用的方法(二)

    第10条:始终要覆盖toString 这一条没什么好讲的,就是说默认的toString方法打印出来的是类名+@+十六进制哈希码的值.我们应该覆盖它,使它能够展示出一些更为详细清晰的信息,这个看实际情况 ...

  2. Effective Java2读书笔记-对于所有对象都通用的方法(三)

    第12条:考虑实现Comparable接口 这一条非常简单.就是说,如果类实现了Comparable接口,覆盖comparaTo方法. 就可以使用Arrays.sort(a)对数组a进行排序. 它与e ...

  3. Effective Java读书笔记--对所有对象都通用的方法

    1.覆盖equals请遵守通用规定.不需要覆写equals的场景:a.类的每个实例都是唯一的.b.类不需要提供"逻辑相等"的测试功能.c.超类已经覆盖了equals的方法.d.类是 ...

  4. Effective Java 学习笔记之所有对象都通用的方法

    一.覆盖equals时请遵守通用约定 1.满足下列任何一个条件时,不需要覆盖equals方法 a.类的每个实例本质上都是唯一的.此时就是Object中equals方法所表达的含义. b.不关心类是否提 ...

  5. [Effective Java 读书笔记] 第三章 对所有对象都通用的方法 第八 ---- 九条

    这一章主要讲解Object类中的方法, Object类是所有类的父类,所以它的方法也称得上是所有对象都通用的方法 第八条 覆盖equals时需要遵守的约定 Object中的equals实现,就是直接对 ...

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

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

  7. [Effective Java]第三章 对所有对象都通用的方法

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  8. Java高效编程之二【对所有对象都通用的方法】

    对于所有对象都通用的方法,即Object类的所有非final方法(equals.hashCode.toString.clone和finalize)都有明确的通用约定,都是为了要被改写(override ...

  9. Effective Java读书笔记——第三章 对于全部对象都通用的方法

    第8条:覆盖equals时请遵守通用的约定 设计Object类的目的就是用来覆盖的,它全部的非final方法都是用来被覆盖的(equals.hashcode.clone.finalize)都有通用约定 ...

随机推荐

  1. 对拍 For Linux

    #!/bin/sh g++ -g gene.cpp -o gene g++ -g a.cpp -o a g++ -g b.cpp -o b while true; do  ./gene > in ...

  2. QQ截图时窗口自动识别的原理(WindowFromPoint, ChildWindowFromPoint, ChildWindowFromPointEx,RealChildWindowFromPoint)

    新版的QQ在截图时加入了窗口自动识别的功能,能根据鼠标的位置自动画出下面窗口的轮廓.今天有人在论坛上问起这个问题,下面我们来探讨这个功能的实现原理. 首先我们要明白截图软件的基本原理,截图时实际上是新 ...

  3. Android Fragment getActivity返回null解决

    在Android开发中,如果我们用到V4包里面的Fragment,在应用被切换到后台的时候,Activity可能被回收,但是创建的所有Fragment则会被保存到Bundle里面,下面是Fragmen ...

  4. Teach Yourself Scheme in Fixnum Days 13 Jump跳转

    Jumps One of the signal features of Scheme is its support for jumps or nonlocal control. Specificall ...

  5. Windows Azure 存储管理器 (2014)

     Windows Azure存储用户经常希望能够在"管理器"中查看他们的数据,管理器指的是一款可用于显示存储帐户数据的工具.我们之前提供了我们所知的存储管理器列表.在本文中,我 ...

  6. tyvj1038忠诚

    描述 Description 老管家是一个聪明能干的人.他为财主工作了整整10年,财主为了让自已账目更加清楚.要求管家每天记k次账,由于管家聪明能干,因而管家总是让财主十分满意.但是由于一些人的挑拨, ...

  7. sgu495:概率dp / 推公式

    概率题..可以dp也可以推公式 抽象出来的题目大意: 有 n个小球,有放回的取m次  问 被取出来过的小球的个数的期望 dp维护两个状态 第 i 次取出的是 没有被取出来过的小球的 概率dp[i] 和 ...

  8. XPath详解

     xPath技术   1 引入 问题:当使用dom4j查询比较深的层次结构的节点(标签,属性,文本),比较麻烦!!! 2 xPath作用 主要是用于快速获取所需的节点对象. 3 在dom4j中如何使用 ...

  9. Python源码学习之初始化(三)-PyDictObject的初始化

    先来看它的定义 typedef struct _dictobject PyDictObject; struct _dictobject { PyObject_HEAD Py_ssize_t ma_fi ...

  10. openwrt上网配置的一些理解

    其实已经有很多帖子讲过openwrt路由器上网配置了,我这里主要是讲我自己的一块硬件路由使用openwrt后的一些上网配置.之所以要研究我自己的配置,是因为硬件,硬件不一样,配置也就不一样,但是总的原 ...