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

我们在覆盖equals方法时,必须遵守它的通用约定:

1.自反性。对于任何非null的引用值x,x.equals(x)必须返回true;

2.对称性。对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true;

一个“不区分大小写”字符串的例子:

public class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s){
if(s == null){
throw new NullPointerException();
}
this.s = s;
}
@Override
public boolean equals(Object obj) {
if(obj instanceof CaseInsensitiveString){
return s.equalsIgnoreCase(((CaseInsensitiveString)obj).s);
}
if(obj instanceof String){
return s.equalsIgnoreCase((String)obj);
}
return false;
}
public static void main(String[] args){
CaseInsensitiveString s1 = new CaseInsensitiveString("xxx");
String s2 = "xxx";
System.out.println(s1.equals(s2)); //true
System.out.println(s2.equals(s1)); //false
}
}

该例子明显违反了对称性。

3.传递性。对于任何非null的引用值x,y和z。如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equas(z)也必须返回true。

一个违反对称性的例子:

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 obj) {
if(!(obj instanceof Point)){
return false;
}
Point p = (Point)obj;
return p.x == x && p.y == y;
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + x;
result = 31 * result + y;
return result;
}
}
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 obj) {
if(!(obj instanceof Point)){
return false;
}
//if obj is a normal Point, do a color-blind comparison
if(!(obj instanceof ColorPoint)){
return obj.equals(this);
}
return super.equals(obj) && ((ColorPoint)obj).color == color;
}
public static void main(String[] args){
ColorPoint p1 = new ColorPoint(1,3, Color.RED);
Point p2 = new Point(1,3);
ColorPoint p3 = new ColorPoint(1,3, Color.BLUE);
System.out.println(p1.equals(p2)); //true
System.out.println(p2.equals(p3)); //true
System.out.println(p1.equals(p3)); //false
}
} enum Color{
RED, GREEN, BLUE;
}

上面的例子提供了对称性,但却牺牲了传递性。我们无法在扩展可实例化类的同时,既增加新的值组件,同时又保留equals约定,除非愿意放弃面向对象的抽象所带来的优势。

4.一致性。对于任何非null的引用值x和y,只要equals的比较操作在对象中所用到的信息没有被改变,多次调用x.equas(y)就会一致地返回true或false;

5.非空性。对于任何非null的引用值x,x.equas(null)必定会返回false。

综上,实现高质量equals方法的诀窍:

1.使用==操作符检查“参数是否为这个对象的引用”.

2.使用instanceof操作符检查“参数是否为正确的类型”。

3.把参数转换为正确的类型。

4.对于该类中的每个“关键字”域,检查参数中的域是否与该对象中的域相匹配。

String类中的equals方法:

    public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}

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

首先明确一个概念,两个对象使用equals返回true,则它们的hashCode也一定相等;如果两个对象的hashCode相等,则它们的equals则不一定相等。

考虑一个简单的PhoneNumber类:

public 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, "lineNumber");
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 obj) {
if(obj == this){
return true;
}
if(!(obj instanceof PhoneNumber)){
return false;
}
PhoneNumber pn = (PhoneNumber) obj;
return pn.lineNumber == lineNumber
&& pn.prefix == prefix
&& pn.areaCode == areaCode;
}
public static void main(String[] args){
Map<PhoneNumber, String> m = new HashMap<>();
m.put(new PhoneNumber(707, 867, 5039), "Kevin");
String name = m.get(new PhoneNumber(707, 867, 5039)); //如果PhoneNumber类不实现hashCode方法,则返回null
System.out.println(name);
}
}

main方法测试返回为null,这是因为该类没有实现hashCode方法,导致两个相等的实例具有不同的散列码。put方法把电话号码对象存放在一个散列通中,而get方法却在另外一个桶中查找这个电话号码。即使两个对象正好被放在一个桶中,get方法也必定会返回为null,因为hashMap有项优化,可以把每个项有关的散列码缓存起来,如果散列码不匹配,也不必检验对象的等同性。所以需要提供一个hashCode方法:

  @Override
public int hashCode() {
int result = 17;
result = 31 * result + areaCode;
result = 31 * result + prefix;
result = 31 * result + lineNumber;
return result;
}

再次用main方法测试,返回“Kevin”。

第10条: 始终要覆盖toString方法

虽然java.lang.Object提供了一个toString方法,但返回的字符串通常不是用户希望的信息。应该返回一个“简洁的,信息丰富,并且易于阅读的表达形式”。在实际应用中,toString方法应该返回对象中包含的所有值得关注的信息譬如之前电话号码的例子:

@Override
public String toString() {
return String.format("(%03d) %03d-%04d", areaCode, prefix, lineNumber);
}

第11条: 谨慎地覆盖clone

Cloneable并没有包含任何方法,那到底有什么作用呢?它决定了Object中受保护clone方法的实现行为,如果一个类实现了cloneable,Object的clone方法就返回该对象中的逐域拷贝,否则就会抛出cloneNotSupportedException异常。clone带来的问题很多,所以可以不用clone方法尽量不用。clone方法也分浅复制和深复制,这里分别举点例子。

浅复制:

public class Stack implements Cloneable{
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16; public Stack(){
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e){
elements[size++] = e;
}
public Object pop(){
if(size == 0){
throw new EmptyStackException();
}
Object result = elements[--size];
elements[size] = null; //清空过期引用,不然会导致内存泄漏
return result;
}
private void ensureCapacity(){
if(elements.length == size){
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
@Override
protected Stack clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
Stack result = (Stack) super.clone();
// result.elements = elements.clone();
return result;
}
public static void main(String[] args) throws CloneNotSupportedException{
Stack s = new Stack();
PhoneNumber pn = new PhoneNumber(111, 222, 3333);
s.push(pn);
Stack s2 = (Stack) s.clone();
System.out.println(s.pop()); // (111) 222-3333
System.out.println(s2.pop()); // null 浅复制,s和s2拥有相同的elements引用,s的pop方法清空了过期引用,所以s2的pop方法返回null
}
}

深复制:

public class HashTable {
private Entry[] buckets;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
private static class Entry{
final Object key;
Object value;
Entry next;
Entry(Object key, Object value, Entry next){
this.key = key;
this.value = value;
this.next = next;
}
//recursively copy the linked list headed by this Entry
//递归方法针对列表中的每个元素,它都要消耗一段内存空间,如果链表比较长,则容易导致栈溢出
//可以用下面的迭代方式进行对象的深复制
// Entry deepCopy(){
// return new Entry(key, value, next == null ? null : next.deepCopy());
// } //iteratively copy the linked list headed by this Entry
Entry deepCopy(){
Entry result = new Entry(key, value, next);
for(Entry p = result; p.next != null; p = p.next){
p.next = new Entry(p.next.key, p.next.value, p.next.next);
}
return result;
}
}
public HashTable(){
buckets = new Entry[DEFAULT_INITIAL_CAPACITY];
} //Broken - results in shared internal state!
/*@Override
protected HashTable clone() throws CloneNotSupportedException {
HashTable result = (HashTable) super.clone();
result.buckets = buckets.clone();
return result;
}*/
@Override
protected HashTable clone() throws CloneNotSupportedException {
HashTable result = (HashTable) super.clone();
result.buckets = new Entry[buckets.length];
for(int i = 0; i < buckets.length; i++){
if(buckets[i] != null){
result.buckets[i] = buckets[i].deepCopy();
}
}
return result; }
}

第12条: 考虑实现Comparable接口

关于Comparable接口其中只有一个方法——compareTo。此方法和equals有类似之处,不过它所表达的含义相比equals要更多。equals通常是比较两个值是否相等,相等返回true,不相等返回false。compareTo则约定为第1对象若“大于”第2个对象则返回整数,“等于”则返回0,“小于”则返回负数,compareTo能约定更为复杂的“比较”,例如比较两个字符串进行字典序的比较,str = “abc”, str2 = “abd”,str.equals(str2)返回false,而str.compareTo(str2)则返回正数。compareTo与equals一样同样需要遵守自反性、对称性、传递性。同样有一个强烈的建议就是compareTo应该返回和equals方法相同的结果,但如果不一致,也不是不可以,就是最好能在注释中写明两个方法返回的结果不同。

CompareTo方法中域的比较是顺序的比较,而不是等同性的比较,比较对象引用域可以递归调用compareTo方法,如果一个域没有实现Comparable接口,或者你需要一个非标准的排序方式,就可以用一个显示的comparator来代替。或者编写自己的comparator,或者使用已有的comparator。

public class CaseInsensitiveString2 implements Comparable<CaseInsensitiveString2>{
private final String s;
public CaseInsensitiveString2(String s){
if(s == null){
throw new NullPointerException();
}
this.s = s;
}
@Override
public boolean equals(Object obj) {
if(obj instanceof CaseInsensitiveString2){
return s.equalsIgnoreCase(((CaseInsensitiveString2)obj).s);
}
if(obj instanceof String){
return s.equalsIgnoreCase((String)obj);
}
return false;
}
public static void main(String[] args){
CaseInsensitiveString2 s1 = new CaseInsensitiveString2("xxx");
String s2 = "xxx";
System.out.println(s1.equals(s2)); //true
System.out.println(s2.equals(s1)); //false
}
@Override
public int compareTo(CaseInsensitiveString2 o) {
// TODO Auto-generated method stub
return String.CASE_INSENSITIVE_ORDER.compare(s, o.s);
}
}

2.对于所有对象都通用的方法_EJ的更多相关文章

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

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

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

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

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

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

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

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

  5. 《Effective Java》第3章 对于所有对象都通用的方法

    第8条:覆盖equals时请遵守通用约定 覆盖equals方法看起来似乎很简单,但是有许多覆盖方式会导致错误,并且后果非常严重.最容易避免这类问题的办法就是不覆盖equals方法,在这种情况下,类的每 ...

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

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

  7. [Java读书笔记] Effective Java(Third Edition) 第 3 章 对于所有对象都通用的方法

    第 10 条:覆盖equals时请遵守通用约定 在不覆盖equals方法下,类的每个实例都只与它自身相等. 类的每个实例本质上都是唯一的. 类不需要提供一个”逻辑相等(logical equality ...

  8. Effective Java 读书笔记之二 对于所有对象都通用的方法

    尽管Object是一个具体的类,但设计它主要是为了扩展.它的所有非final方法都有明确的通用约定.任何一个类在override时,必须遵守这些通用约定. 一.覆盖equals时请遵守通用的约定 1. ...

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

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

随机推荐

  1. 利用Swashbuckle生成Web API Help Pages

    利用Swashbuckle生成Web API Help Pages 本文将通过Swagger的.NET Core的实现封装工具Swashbuckle来生成上一篇-<创建ASP.NET Core ...

  2. 1分钟快速制作漂亮的H5本地记事本

    大家好,以前给大家分享过一个五步骤制作精美的HTML5时钟的文章,点击回顾<五步教你制作漂亮精致的HTML时钟>,还有<一分钟教你如何实现唯美的文字描边>:今天给大家分享一个用 ...

  3. 站在JAVA数据结构的视角看待简单表结构

    1.前言: 我们提到程序中的集合的时候,往往脑海中会浮现出, 如ArrayList和LinkedList以及和HashMap.当然在提到ArrayList和LinkedList的时候,我们大多数的人都 ...

  4. 第87节:Java中的Bootstrap基础与SQL入门

    第87节:Java中的Bootstrap基础与SQL入门 前言复习 什么是JQ? : write less do more 写更少的代码,做更多的事 找出所有兄弟: $("div" ...

  5. canvas转图片中的文字自动换行

    概述 最近项目用到了canvas转图片,但是由于canvas对文字排版的支持非常弱,一般我们在canvas上画不同排版的文字(比如竖排文字)都是利用js计算横纵坐标,然后一个字一个字地画出来,今天无意 ...

  6. 客户端ip获取蹲坑启示: 不要侥幸

    怎么获取一个客户端ip ? 我想这个问题,在网上遍地都是答案! 而且多半是像下面这样: public static String getIpAddress(HttpServletRequest req ...

  7. Vue SSR不可不知的问题

    Vue SSR不可不知的问题 本文主要介绍Vue SSR(vue服务端渲染)的应用场景,开发中容易遇到的一些问题,提升ssr性能的方法,以及ssr的安全性问题. ssr的应用场景 1.SEO需求 SE ...

  8. HTML5拍照、摄像机功能实战

    HTML5拍照.摄像机功能实战 苏格团队 作者:Tomey 开篇 最近在做一个chrome app的云相机应用,应用包括拍照.摄像.保存照片视频.上传文件等等核心功能,其中涉及到很多HTML5对媒体流 ...

  9. vue属性监听

    1.watch:用来监听每一个属性的变化 2.watch这个对象里面都是函数,函数的名称是data中的属性名称,watch中的函数不需要调用 3.当属性发生改变那么就会触发watch函数,每个函数都会 ...

  10. mysql 开发进阶篇系列 21 磁盘I/O问题(RAID)

    一.概述 作为应用系统的持久化层,不管数据库采取了什么样的Cache机制,数据库最终总是要将数据储存到可以长久保存的I/O设备磁盘上.但磁盘的存取速度显然要比cpu,ram的速度慢很多.因此,对于比较 ...