第8条:覆盖equals时请遵守通用约定:
以下四种情况,默认实现正是所期望的结果:
* 类的每个实例本质上是唯一的。
* 不关心类是否提供了逻辑相等的测试功能
* 超类已经覆盖了equals方法,继承过来的子类同样适用的
* 类是私有的或者包级私有的,可以确定它的equals方法永远不会被调用,那么请覆盖这个equals方法:
@Override public boolean equals(Object o) { throw new AssertionError(); // Method is never called }
什么时候我们应该正常覆盖equals方法呢?
如果类具有自己特有的逻辑相等概念,而且超类还没有覆盖equals以实现期望的行为。这通常属于值类的情形。
JavaSE6规范中定义了equals的约定,必须实现等价关系 equivalence relation:
* 自反性 reflexive。对于任何非null引用x,x.equals(x)返回true
* 对称性 symmetric。对于任何非null x和y ,。。。
* 传递性 transtive。对于任何非null x、y、z。。。
* 一致性 consistent。对于任何非null x和y,对此调用x.equals(y),返回结果一样
* 对于任何非null x,x.equals(null)返回false
实现高质量equals方法的诀窍:
* 使用==操作符检查参数是否为这个对象的引用,如果是,返回true
* 使用instanceof 操作符检查参数是否为正确的类型,如果不是,返回false
* 把参数转换成正确的类型,之前用instanceof进行了测试
* 对于该类中的每个关键significant域,检查参数中域是否与该对象中对应匹配,全部测试成功后,返回true,否则返回false。
对于既不是flast,也不是double类型的基本数据类型域,可以使用==操作符比较,对于对象引用域,可以递归地使用equals,对于float域,可以使用Float.compare方法,对于double域,则使用Double.compare。对于数组的话,可以使用Arrays.equals方法。
对于可以包含null的域,下面的习惯用法:
(field==null ? o.field==null : field.equals(o.field))
最后,还有一些忠告:
-- 覆盖equals时总要覆盖hashCode
-- 不要企图让equals方法过于智能
-- 不要把equals方法的Object 参数改成其他的,不然就不是覆盖equals方法了,而只是重载。
-- 添加@Override注解
第9条:覆盖equals时总要覆盖hashCode方法:
1. 把某个非0的常数值比如17,保存在一个名为result的int类型变量中。
2. 对于对象中每个关键域f(指equals方法中涉及的域),完成以下步骤:
a. 为该域计算int类型散列码c:
①. 如果该域是boolean类型,则计算(f ? 1 : 0)
②. 如果该域是byte、char、short、int类型,则计算(int)f
③. 如果该域是long类型,计算(int) (f^(f>>>32))
④. 如果该域是float类型,计算Float.floatToIntBits(f)
⑤. 如果该域是double类型,计算Double.doubleToLongBits(f),返回按照步骤2.a.③为得到的long类型计算散列值。
⑥. 如果该域是一个对象引用,并且该类的equals方法通过递归调用equals方式比较这个域,则同样为这个域递归调用hashCode。如果这个域值为null,则返回0
⑦. 如果该域是数组,对于每个元素,递归调用上述规则,然后根据2.b做法将这些散列值组合起来。如果数组中每个元素都很重要,可以利用Arrays.hashCode方法。
b. 按照下面的公式,把步骤2.a中计算得到的散列值c合并到result中:
result = 31*result + c;
3. 返回result
4. 写完后不要忘记用单元测试来验证下
一个小例子:
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; } // Broken - no hashCode method! @Override public int hashCode() { int result = 17; result = 31 * result + areaCode; result = 31 * result + prefix; result = 31 * result + lineNumber; return result; } // Lazily initialized, cached hashCode - Page 49 // private volatile int hashCode; // (See Item 71) // // @Override public int hashCode() { // int result = hashCode; // if (result == 0) { // result = 17; // result = 31 * result + areaCode; // result = 31 * result + prefix; // result = 31 * result + lineNumber; // hashCode = result; // } // return result; // } 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))); } }
第10条:始终要覆盖toString:
这个没啥好讲的
第11条:谨慎的覆盖clone:
从jdk1.5开始,引入了协变返回类型 covariant return type作为泛型,换句话说,就是目前覆盖方法的返回类型可以是被覆盖方法的返回类型的子类了。
所有实现了Cloneable接口的类应该用一个公有方法覆盖clone。此公有方法先调用super.clone,然后修正任何需要修正的域。
最佳实践是提供一个类似于拷贝构造器的静态工厂:
public static Yum newInstance(Yum yum);
由于clone方法有许多的缺点,专家级程序员干脆不去覆盖clone方法,也从来不去调用它,除非拷贝数组。
第12条:考虑实现Comparable接口:
比较基本整数类型时候使用> <等,浮点域使用Double.compare()或者Float.compare()
本人博客已搬家,新地址为:http://yidao620c.github.io/
相关推荐
### 第三章 对所有对象都通用的方法 这部分主要关注Java中的基本对象行为: 1. **equals方法**:正确覆写`equals`以满足等价关系,同时要遵循一致性和传递性原则。 2. **hashCode方法**:当覆写`equals`时,必须同时...
《Effective Java》是Java开发领域的经典著作,作者...以上仅是《Effective Java》的部分内容,书中还有更多关于枚举、泛型、集合、多线程等方面的重要指导,都是Java开发者提升代码质量、遵循良好编程习惯的宝贵资源。
这些知识点都是 Java 编程语言的核心内容,了解和掌握这些知识点对于编写高质量的 Java 代码至关重要。 《Effective Java》读书分享.pptx 是一本非常实用的 Java 编程语言指南,涵盖了 Java 编程语言的方方面面,对...
- 序列化:解释了Java对象如何被序列化和反序列化,以及序列化接口的作用。 - 自定义序列化:讨论了如何通过实现`writeObject()`和`readObject()`方法来自定义序列化行为。 - 隐式序列化风险:提到了序列化可能...
以上只是《Effective Java》第二版部分关键知识点的概述,实际上,书籍中还包含更多关于类设计、方法设计、多线程、集合框架等方面的深入讨论和建议,是每个Java开发者不可或缺的参考书。通过阅读并实践书中的建议,...
Joshua Bloch 所著《Effective Java 第二版》一书摘要这是我对 Joshua Bloch 所著... 所有对象通用的方法8. 覆盖equals时遵守一般约定9.重写equals时务必重写hashCode10. 始终覆盖toString11.明智地覆盖克隆12.考虑实
目录:一、创建和销毁对象 (1 ~ 7)二、对于所有对象都通用的方法 (8 ~ 12)三、类和接口 (13 ~ 22)四、泛型 (23 ~ 29)五、枚举和注解 (30 ~ 37)六、方法 (38 ~ 44)七、通用程序设计 (45 ~ 56)八、异常 ...
3. **方法覆盖和重载**:Item 26 "优先考虑使用泛型方法" 提示我们如何通过泛型提高方法的通用性,而Item 27 "避免在重写方法中抛出新异常或不抛出异常" 关注的是多态性的正确实现。 4. **异常处理**:Item 36 "尽...
所有对象通用的方法 04 - 类和接口 05 - 泛型 06 - 枚举和注释 07 - Lambda 和流 08 - 方法 09 - 通用编程 10 - 例外 11 - 并发 12 - 序列化 第 2 章 - 创建和销毁对象 第 1 项 - 考虑静态工厂方法而不是构造函数 ...
根据提供的信息,“Effective Java 中文版 第二版”这本书主要关注的是Java编程语言的最佳实践、设计模式以及如何编写高效、可维护的Java代码。虽然给定的部分内容并未提供实际的文字内容,但从标题和描述来看,我们...
第三章所有对象的通用方法 项目编号 标题 副标题 经理 项目10 通过遵循一般约定重新定义等于 海蒂 项目11 如果要重新定义等于,则也重新定义hashCode 莉娜 项目12 始终重新定义toString 林 项目13
- **类与对象**:Java中的所有事物都是通过类来定义的,而对象则是这些类的具体实例。 - **数据类型**:Java提供了多种内置的数据类型,包括基本数据类型如int、float等,以及引用数据类型如数组和用户自定义的类。 ...
《有效的Java》是Java开发领域的一本经典著作,由Joshua Bloch撰写,它为Java程序员提供了一系列实用的编程准则和最佳实践。本书的核心理念是提高代码质量、可读性和可维护性,通过一系列条目介绍了如何编写更高效、...
#### 三、所有对象通用的方法 ##### Item7:在覆盖`equals`方法时遵循一般契约 - **目的**:确保`equals`方法的一致性和正确性。 - **实现方式**: - 在覆盖`equals`方法时,需确保遵循其一般契约,包括自反性、...
- **O/R Mapping**:对象关系映射,用于将数据库表映射为Java对象。 - **实现**:通过配置文件和注解指定映射关系。 2. **如何不改代码解决数据源更换的问题** 通过配置文件灵活配置不同的数据源,例如使用...
- **同步机制**: `Hashtable`的所有方法都是同步的,而`HashMap`提供了一个同步的子类`ConcurrentHashMap`。 #### 六、九大隐式对象 - `page`: 当前页面上下文。 - `pageContext`: 当前页面上下文对象。 - `...
- **基本图形类**: 所有的图形按钮继承自一个共同的父类`drawings`,这个父类定义了一些通用的属性和方法,例如坐标、颜色、线条粗细等。 - 属性包括坐标(x1, x2, y1, y2)、颜色(R, G, B)、线条粗细(stroke)、图形...
在深入探讨Java开发注意事项之前,我们首先应当澄清,给定的部分内容似乎包含了非文本或乱码信息...以上注意事项涵盖了从编码规范到系统架构的多个方面,对于任何Java开发者而言,都是值得深入了解和实践的关键知识点。