第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. HNOI2019退役记

    退役 警告. 今年虽然我没有变强, 但是还是比去年强一点(去年树形dp都不会). Day0 明天就是省选了,晚上22:13还没有睡觉,真的是状态不好. 打了一下LCT的板子,发现终于理解了...(这叫 ...

  2. Hadoop 电话通信清单

    一.实例要求 现有一批电话通信清单,记录了用户A拨打某些特殊号码(如120,10086,13800138000等)的记录.需要做一个统计结果,记录拨打给用户B的所有用户A. 二.测试样例 样例输入: ...

  3. 微信小程序中如何使用WebSocket实现长连接(含完整源码)

    本文由腾讯云技术团队原创,感谢作者的分享. 1.前言   微信小程序提供了一套在微信上运行小程序的解决方案,有比较完整的框架.组件以及 API,在这个平台上面的想象空间很大.腾讯云研究了一番之后,发现 ...

  4. python写的计算器

    import tkinter #导入tkinter模块 root = tkinter.Tk()root.minsize(280,500)root.title('xx的计算器') #1.界面布局#显示面 ...

  5. Git使用详细教程(3):git add, git commit详解

    在使用git之前,我们首先要初始化一个git管理的仓库,这里以博客(blog)为例 git init blog 我们进入目录,执行git status查看git状态,可以看到一个新的git管理的项目目 ...

  6. LabVIEW(九):程序结构中的分支结构和顺序结构

    一.分支结构 1.创建分支结构:程序框图右键>结构>条件结构 2.Ctrl + I 会显示错误列表,双击错误列表会定位到该错误在程序框图中地方. 3.有的分支可以不连接分支内容. 在不连接 ...

  7. python读取xml文件示例

    最近用到了xml格式的文件,对这些没啥印象,顺便学习一下,做个例子,方便后续查阅. xml文档: <annotation> <folder>VOC2012</folder ...

  8. 多线程 Thread.yield 方法到底有什么用?

    概念 我们知道 start() 方法是启动线程,让线程变成就绪状态等待 CPU 调度后执行. 那 yield() 方法是干什么用的呢?来看下源码. /** * A hint to the schedu ...

  9. ModelState 错误信息输出

    在MVC的项目中,我们通常情况下,为了方便(偷懒),会直接使用 !ModelState.IsValid 来判断实体的验证是否正确,但是这样对于用户的体验是不好的,当填写的内容比较多的时候,用户需要自己 ...

  10. 容器、容器集群管理平台与 Kubernetes 技术漫谈

    原文:https://www.kubernetes.org.cn/4786.html 我们为什么使用容器? 我们为什么使用虚拟机(云主机)? 为什么使用物理机? 这一系列的问题并没有一个统一的标准答案 ...