本章内容:
1. 覆盖equals时请遵守通用约定
2. 覆盖equals时总要覆盖hashCode
3. 始终要覆盖toString
4. 谨慎地覆盖clone
5. 考虑实现Comparable接口
1. 覆盖equals时请遵守通用约定
如果不覆盖equals方法,类的每个实例都只与它自身相等。以下类则不需要覆盖equals方法:
类的每个实例本质上都是唯一的;
不关心类是否提供了“逻辑相等”的测试功能;
超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的;
类是私有的或是包级私有的,可以确定它的equals方法永远不会被调用。
如果类具有自己特有的“逻辑相等”概念(非对象等同的概念),而且超类还没有覆盖equals实现期望的行为,这时我们就需要覆盖equals方法。这通常属于“值类”的情形,一个仅仅表示值的类。
覆盖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,只要比较的对象中所用的信息没有被修改,多次调用x.equals(y)返回结果必须一致。
对于任何非null的引用值x,x.euqals(null)必须返回false。
里氏替换原则,一个类型的任何重要属性也将适用于它的子类型,因此为该类型编写的任何方法,在它的子类型上也应该同样运行得很好。
如何实现高质量的equals方法
(1)使用==操作符检查“参数是否为这个对象的引用”,如果是,则返回true。这只不过是一种性能优化,如果比较操作符有可能很昂贵就值得这么做。
(2)使用instanceof(判断其左边对象是否为其右边类的实例)操作符检查“参数是否为正确的类型”,如果不是,则返回false。
(3)把参数转换成正确的类型
(4)对于该类中的每个“关键”域,检查参数中的域是否与该对象中对应的域相匹配。
(5)当你编写完成了equals方法之后,应该问自己三个问题:它是否对称的、传递的、一致的?
示例如下:
@Override
public boolean equals(Object o){
if(o == this) return true;
if(!(o instanceof A)) return false;
A a = (A)o;
return a.id == id && a.name == name;
}
2. 覆盖equals时总要覆盖hashCode
在每个覆盖了equals方法的类中,也必须覆盖hashCode方法。如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常动作,这样的集合包括HashMap、HashSet和Hashtable。
JavaSE6规范约定如下:
(1)只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法都必须始终如一地返回同一个整数。
(2)如果两个对象根据equals方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。
(3)如果两个对象根据equals方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生不同的整数结果。但是程序员应该知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表的性能。
理想情况下,散列函数应该把集合中不相等的实例均匀地分布到所有可能的散列值上。下面给出一种简单的解决方法:
(1)把某个非零的常数值,比如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),然后按long类型计算出int值;
对象类型:根据上面的计算方法递归的计算此对象类型的hashCode值,或定义一个更复杂的范式来计算。对象为null时通常返回0;
数据:把每一个域当做单独的域来处理,为每个元素计算一个散列值。
b. 按照下面的公式,把步骤2.a中计算得到的散列码c合并到result中
result = 31 * result + c; // 31是一个奇素数,好处是可以用移位和减法来代替乘法,VM可以自动完成这种优化。
(3)返回result
(4)写完hashCode方法之后,一定要问问自己“相等的实例是否具有相等的散列码”。
示例如下:
@Override
public int hashCode(){
int result = 17;
result = 31 * result + id;
result = 31 * result + age;
return result;
}
如果一个类是不可变的,并且计算散列码的开销也比较大,就应该考虑把散列码缓存在对象内部,而不是每次请求的时候都重新计算散列码。如果你觉得这种类型的大多数对象会被用做散列键,就应该在创建实例的时候计算散列码,否则可以选择“延迟初始化(lazily initialize)”散列码,一直到hashCode被第一次调用的时候才初始化。
3. 始终要覆盖toString
虽然java.lang.Object提供了toString方法的一个实现,但它返回的字符串通常并不是类用用户所期望看到的。它包含类的名称,以及一个@符号,接着是散列码的无符号十六进制表示法。
toString的通用约定指出,被返回的字符串应该是一个“简洁的,但信息丰富,并且易于阅读的表达形式”。
示例如下:
@Override
public String toString(){
return "A[id="+id+",name="+name+"]";
}
4. 谨慎地覆盖clone
Cloneable接口的目的是作为对象的一个mixin接口,表名这样的对象允许克隆。遗憾的是,它并没有成功地达到这个目的,主要的缺陷在于它缺少一个clone方法,Object的clone方法是受保护的,如果不借助于反射,就不能仅仅因为一个对象实现了Cloneable就可以调用clone方法。即使是反射调用也可能会失败,因为不能保证该对象一定具有可访问的clone方法。
Cloneable并没有包含任何方法,它决定了Object中受保护的clone方法实现的行为:如果一个类实现了Cloneable,Object的clone方法就返回该对象的逐域拷贝,否则就会抛出CloneNotSupportedException异常。
java.lang.Object规范中对Clone方法的约定如下:
创建和返回该对象的一个拷贝。对于任何对象x,表达式 x.clone != x 将会是true,并且表达式 x.clone().getclass() == x.getClass()将会是true(父子类不满足),但这些都不是绝对的要求。虽然通常情况下,表达式 x.clone().equals(x) 将会是true,但这也不是一个绝对的要求。拷贝对象往往会导致创建它的类的一个新实例,但它同时也会要求拷贝内部的数据结构。这个过程中没有调用构造器。
另一个实现对象拷贝的好办法是提供一个拷贝构造器或拷贝工厂。拷贝构造器只是一个构造器,它唯一的参数类型是包含该构造器的类:
public A(A a);
拷贝工厂是类似于拷贝构造器的静态工厂:
public static A newInstance(A a);
拷贝构造器及静态工厂方法都比Cloneable/clone方法具有更多的优势:它们不依赖于某一种很有风险的、语言之外的对象创建机制;它们不要求遵守尚未制定好文档的规范;它们不会与final域的正常使用发生冲突;它们不会抛出不必要的受检异常;它们不需要进行类型转换。
5. 考虑实现Comparable接口
compareTo方法是Comparable接口唯一的方法,它不但允许进行简单的等同性比较,而且允许执行顺序比较。类实现了Comparable接口,就表明它的实例具有内在的排序关系。
一旦类实现了Comparable接口,它就可以跟许多泛型算法以及依赖于该接口的集合实现进行协作。你付出很小的努力就可以获得非常强大 的功能。如果你正在编写一个值类,它具有非常明显的内在排序关系,比如字母顺序、按数值顺序或者按年代顺序,那你就应该坚决考虑实现 这个接口。
compareTo方法的通用约定如下:
将这个对象与指定的对象进行比较,当该对象小于、等于或大于指定对象的时候 ,分别返回一个负整数、零或者正整数。如果由于指定对象的类型而无法与该对象进行比较,则抛出ClassCastException异常。
同时它也应该满足自反性、对象性和传递性。
强烈建议(x.compareTo(y) ==0) == (x.equals(y)),但这并非绝对必要。一般任何实现了Comparable接口的类,若违反了这个条件,都应该明确予以说明“注意:该类具有内在的排序功能,但是与equals不一致”。
如果一个类有多个关键域,必须从最关键的域开始,逐步进行到所有的重要域。如果某个域的比较产生了非零的结果,则整个比较操作结束,并返回该结果。如果所有的域都是相等的,则对象就是相等的,并返回零。示例如下:
public int compareTo(A a){
if(id < a.id) return -1;
if(id > a.id) return 1;
if(age < a.age) return -1;
if(age > a.age) return 1;
...
return 0;
}
改进如下(保证两个数的差不会溢出):
public int compareTo(A a){
int idDiff = id - a.id;
if(idDiff != 0) return idDiff;
int ageDiff = age - a.age;
if(ageDiff != 0) return ageDiff;
...
return 0;
}
分享到:
相关推荐
所有对象通用的方法 04 - 类和接口 05 - 泛型 06 - 枚举和注释 07 - Lambda 和流 08 - 方法 09 - 通用编程 10 - 例外 11 - 并发 12 - 序列化 第 2 章 - 创建和销毁对象 第 1 项 - 考虑静态工厂方法而不是构造函数 ...
### 第三章 对所有对象都通用的方法 这部分主要关注Java中的基本对象行为: 1. **equals方法**:正确覆写`equals`以满足等价关系,同时要遵循一致性和传递性原则。 2. **hashCode方法**:当覆写`equals`时,必须同时...
《Effective Java》是Java开发领域的经典著作,作者Joshua Bloch深入浅出地阐述了编写高效、健壮的Java代码的技巧和最佳实践。以下是对该书部分内容的详细解释: 1. **产生和销毁对象** - Item1:静态工厂方法相比...
其他知识点还包括泛型、枚举和注解、Lambda 和 Stream、方法、通用编程、异常、并发、序列化等。这些知识点都是 Java 编程语言的核心内容,了解和掌握这些知识点对于编写高质量的 Java 代码至关重要。 《Effective ...
- 序列化:解释了Java对象如何被序列化和反序列化,以及序列化接口的作用。 - 自定义序列化:讨论了如何通过实现`writeObject()`和`readObject()`方法来自定义序列化行为。 - 隐式序列化风险:提到了序列化可能...
3. **方法覆盖和重载**:Item 26 "优先考虑使用泛型方法" 提示我们如何通过泛型提高方法的通用性,而Item 27 "避免在重写方法中抛出新异常或不抛出异常" 关注的是多态性的正确实现。 4. **异常处理**:Item 36 "尽...
第三章所有对象的通用方法 项目编号 标题 副标题 经理 项目10 通过遵循一般约定重新定义等于 海蒂 项目11 如果要重新定义等于,则也重新定义hashCode 莉娜 项目12 始终重新定义toString 林 项目13
目录:一、创建和销毁对象 (1 ~ 7)二、对于所有对象都通用的方法 (8 ~ 12)三、类和接口 (13 ~ 22)四、泛型 (23 ~ 29)五、枚举和注解 (30 ~ 37)六、方法 (38 ~ 44)七、通用程序设计 (45 ~ 56)八、异常 ...
java8集合源码有效的 Kotlin - 示例 免责声明:这个存储库包含我在阅读优秀书籍 Effective Kotlin 时收集的个人笔记和示例。 它绝不是本书的摘录或副本。 我鼓励所有 Kotlin 开发人员购买和阅读这本书。 第 1 部分:...
以上只是《Effective Java》第二版部分关键知识点的概述,实际上,书籍中还包含更多关于类设计、方法设计、多线程、集合框架等方面的深入讨论和建议,是每个Java开发者不可或缺的参考书。通过阅读并实践书中的建议,...
- **O/R Mapping**:对象关系映射,用于将数据库表映射为Java对象。 - **实现**:通过配置文件和注解指定映射关系。 2. **如何不改代码解决数据源更换的问题** 通过配置文件灵活配置不同的数据源,例如使用...
- **默认方法**:Java 8引入的接口默认方法允许在不破坏已有实现的情况下扩展接口。 7. **异常处理** - **异常链**:使用`cause`属性记录异常的起因,便于调试。 - **不要忽视异常**:避免使用`catch (Exception...
#### 三、所有对象通用的方法 ##### Item7:在覆盖`equals`方法时遵循一般契约 - **目的**:确保`equals`方法的一致性和正确性。 - **实现方式**: - 在覆盖`equals`方法时,需确保遵循其一般契约,包括自反性、...
通过以上对“Effective Java 中文版 第二版”的核心知识点的总结,我们可以看到这本书覆盖了Java编程语言的各个方面,包括面向对象设计原则、类与接口的设计、对象的创建与销毁、枚举类型与注解、泛型与集合框架以及...
《唯品会Java开发手册1.0.2版本》是对阿里巴巴Java开发手册的补充和精简,结合了《Clean Code》和《Effective Java》等经典著作的精华,旨在为Java开发者提供一套符合唯品会实际需求的编码规范。手册涵盖命名、格式...
- **项03:覆盖equals时请遵守通用约定**:正确的equals方法实现对于确保对象一致性至关重要。 #### 代码设计原则 - **项04:始终覆盖hashCode**:当重写了equals方法时,必须同时重写hashCode方法以保持一致性。 ...
- Java编程书籍: 如《Thinking in Java》、《Effective Java》等。 - 在线教程和博客: 许多开发者分享的实践经验和技术文章。 综上所述,Java绘图板项目的开发不仅是一项实际的编程任务,更是深入学习Java语言...
- **《Effective Java》**: 关于Java编程的最佳实践。 - **《Clean Code》**: 关于编写清晰简洁代码的原则和技术。 - **《Design Patterns: Elements of Reusable Object-Oriented Software》**: 介绍设计模式的经典...