第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. SqlServer2008 新功能:简单数据加密

    一.首先要把密码字段改成 varbinary 类型. CREATE TABLE [dbo].[UserInfo]( [id] [int] IDENTITY(1,1) NOT NULL, [name] ...

  2. 如何设置让外网通过路由器IP加端口号访问到局域网一台Web服务器

    场景描述: 我们局域网内所有主机链接一台路由器,通过设置动态获取IP上网,现在想让一台主机作为Web 服务器,让外网用户通过http://ip:port的方式访问. 1:首先修改Apache的端口号: ...

  3. LeetCode_sqrt(x)

    class Solution { public: int sqrt(int x) { // Start typing your C/C++ solution below // DO NOT write ...

  4. LeetCode_Maximal Rectangle

    Given a 2D binary matrix filled with 0's and 1's, find the largest rectangle containing all ones and ...

  5. 关于volatile

    也许读者会注意到,端口寄存器的变量使用了volatile 修饰符,这是因为C语言在编译的时候会进行某种优化来提高效率,比如下面这段代码int i = 1;int a = i;int b = i;对于编 ...

  6. MySQL常用时间函数

    官方文档:Date and Time Functions Name Description ADDDATE() Add time values (intervals) to a date value ...

  7. linux 命令后台执行

    我想把updatedb命令(用于重新建立整盘索引的命令)放在后台运行.因为我不想眼睁睁的看着机器建立索引,我还想编会儿程序呢: # updatedb & [1] 23336 注释:在所要执行的 ...

  8. docker 1.10.3 里php出现 curl 56错误码问题解决

    http://www.cnblogs.com/fengwei/p/5899018.html

  9. 前端模拟发送数据-Chrome下的REST Client

    1)确定需要POST的数据 2)拼接数据,POST给服务器 3)查看服务器响应及结果

  10. 栈和托管堆/值类型和引用类型/强制类型转换/装箱和拆箱[C#]

    原文地址:http://www.cnblogs.com/xy8.cn/articles/1227228.html 一.栈和托管堆      通用类型系统(CTS)区分两种基本类型:值类型和引用类型.它 ...