第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。

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

  1. public final class CaseInsensitiveString {
  2. private final String s;
  3.  
  4. public CaseInsensitiveString(String s) {
  5. if (s == null)
  6. throw new NullPointerException();
  7. this.s = s;
  8. }
  9. @Override
  10. public boolean equals(Object o) {
  11. if (o instanceof CaseInsensitiveString)
  12. return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
  13. if (o instanceof String)
  14. return s.equalsIgnoreCase((String) o);
  15. return false;
  16. }
  17.  
  18. public static void main(String[] args) {
  19. CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
  20. String s = "polish";
  21. System.out.println(cis.equals(s) + " " + s.equals(cis));
  22. }
  23. }

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

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

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

  1. //点类
  2. public class Point {
  3. private final int x;
  4. private final int y;
  5.  
  6. public Point(int x, int y) {
  7. this.x = x;
  8. this.y = y;
  9. }
  10.  
  11. @Override
  12. public boolean equals(Object o) {
  13. if (!(o instanceof Point))
  14. return false;
  15. Point p = (Point) o;
  16. return p.x == x && p.y == y;
  17. }
  18.  
  19. @Override
  20. public int hashCode() {
  21. return 31 * x + y;
  22. }
  23. }

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

  1. public class ColorPoint extends Point {
  2. private final Color color;
  3.  
  4. public ColorPoint(int x, int y, Color color) {
  5. super(x, y);
  6. this.color = color;
  7. }
  8.  
  9. @Override
  10. public boolean equals(Object o) {
  11. if (!(o instanceof ColorPoint))
  12. return false;
  13. return super.equals(o) && ((ColorPoint) o).color == color;
  14. }
  15.  
  16. public static void main(String[] args) {
  17. ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
  18. Point p2 = new Point(1, 2);
  19. ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
  20. System.out.printf("%s %s %s%n", p2.equals(p1), p2.equals(p3),
  21. p1.equals(p3));
  22. }
  23. }
  24.  
  25. public enum Color {
  26. RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
  27. }

这时,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条的诀窍写出来的。

  1. public final class PhoneNumber {
  2. private final short areaCode;
  3. private final short prefix;
  4. private final short lineNumber;
  5.  
  6. public PhoneNumber(int areaCode, int prefix, int lineNumber) {
  7. rangeCheck(areaCode, 999, "area code");
  8. rangeCheck(prefix, 999, "prefix");
  9. rangeCheck(lineNumber, 9999, "line number");
  10. this.areaCode = (short) areaCode;
  11. this.prefix = (short) prefix;
  12. this.lineNumber = (short) lineNumber;
  13. }
  14.  
  15. private static void rangeCheck(int arg, int max, String name) {
  16. if (arg < 0 || arg > max)
  17. throw new IllegalArgumentException(name + ": " + arg);
  18. }
  19.  
  20. @Override
  21. public boolean equals(Object o) {
  22. if (o == this)
  23. return true;
  24. if (!(o instanceof PhoneNumber))
  25. return false;
  26. PhoneNumber pn = (PhoneNumber) o;
  27. return pn.lineNumber == lineNumber && pn.prefix == prefix
  28. && pn.areaCode == areaCode;
  29. }
  30.  
  31. public static void main(String[] args) {
  32. Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
  33. m.put(new PhoneNumber(707, 867, 5309), "Jenny");
  34. System.out.println(m.get(new PhoneNumber(707, 867, 5309)));
  35. }
  36. }

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

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

  1. @Override
  2. 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. About Undefined Behavior[译文]

    原文:blog.llvm.org/2011/05/what-every-c-programmer-should-know.html 人们偶尔会问为什么LLVM的汇编代码有时会在优化器打开时产生SIGT ...

  2. JAVA在线基础教程!

    http://www.runoob.com/java/java-tutorial.html http://www.51zxw.net/list.aspx?cid=380 http://www.weix ...

  3. hdu4622-Reincarnation(后缀自动机)

    Problem Description Now you are back,and have a task to do:Given you a string s consist of lower-cas ...

  4. c指针点滴1

    #include <stdio.h> #include <stdlib.h> void main() { ; int *p = &num;//&num是一个地址 ...

  5. 详细介绍如何使用kindEditor编辑器

    今天群里的朋友问我能不能写个kindEditor编辑器的使用教程,说是弄了半天没有搞定.由于PHP啦后台正好用了这个编辑器,我有写经验,正好教他的同时写出来分享给大家. kindEditor编辑器是一 ...

  6. MyBatis初学者配置

    小配置 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC &qu ...

  7. css中的段落样式及背景

    一.段落样式 css中关于段落的样式主要有行高,缩进,段落对齐,文字间距,文字溢出,段落换行等.它们的具体语法如下: line-height : normal | length text-indent ...

  8. (转)直接保存对象的数据库——db4o

    在实际开发中,数据的存储是必不可少的,常用的有数据库存储和文件存储.数据库目前有关系型数据库和文档型数据库(No-SQL).关系型数据库以字段.类型.约束.表关系来存储和管理数据,比较常见的比如Ora ...

  9. java.lang.NoSuchFieldError: deferredExpression解决

       java.lang.NoSuchFieldError: deferredExpression这个问题的出现是在的lib下面有多个版本的jstl.jar包,解决办法很简单,只留下一个版本的jstl ...

  10. 漫话Unity3D(一)

    前言 使用Unity已经有将近半年时间了,尽管项目还仅仅是个半成品,可是Unity差点儿相同玩熟了.这里把使用过程中碰到的问题梳理一遍.不会涉及到太过详细的功能和代码,可是假设开发一个网游又都会涉及到 ...