第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. 阿里,百度面试90%会问的Java面试题

    题目一 请对比 Exception 和 Error,另外,运行时异常与一般异常有什么区别? 考点分析: 分析 Exception 和 Error 的区别,是从概念角度考察了 Java 处理机制.总的来 ...

  2. Winginx nginx 启动提示80端口被占用

    第一步:查看80端口占用信息 win键+R运行命令:cmd-->netstat -aon|findstr "80" 2.结束任务 找到  pin=4272这个进程,将进程结束 ...

  3. 解决spring的bean同名冲突

    今天工作发现当不同模块下有相同bean然后又被同一个模块引用的话就会导致bean同名冲突,如下: 解决方案很简单,如果是xml配置直接修改bean的名称即可,如果是注解形式修改如下: 只要在servi ...

  4. Kibana简单使用教程

    ELK平台日志查看教程 1. 访问地址:http://xxx:5601/app/kibana 我们主要使用的是右边Discover功能,默认显示的是183tpp(可设置)最近15分钟日志信息. 2. ...

  5. stack源码

    stack概述 stack是一种先进后出的数据结构,它只有一个出口,允许新增元素.移除元素.取得最顶端元素,但每次只能处理顶端元素,也就是说,stack不允许遍历行为. stack定义 以某种既有容器 ...

  6. mysql 开发基础系列3 日期数据类型

    日期类型 如果要用来表示年月日,通常用DATE 来表示. 如果要用来表示年月日时分秒,通常用DATETIME 表示. 如果只用来表示时分秒,通常用TIME 来表示. TIMESTAMP表示格式 :YY ...

  7. 【python】re库 正则的一些过滤和把str拆分成list案例 以及json dict类型

    0x01: 部分参考:https://www.cnblogs.com/edwardsun/p/4421773.html match(string[, pos[, endpos]]) | re.matc ...

  8. 第二次作业:分布式版本控制系统Git的安装与使用

    本次作业要求来自:https://edu.cnblogs.com/campus/gzcc/GZCC-16SE1/homework/2103 第一个git仓库地址:https://github.com/ ...

  9. Apache-Flink深度解析-TableAPI

    您可能感兴趣的文章合集: Flink入门 Flink DataSet&DataSteam API Flink集群部署 Flink重启策略 Flink分布式缓存 Flink重启策略 Flink中 ...

  10. Unity3D热更新之LuaFramework篇[02]--用Lua创建自己的面板

    在上篇文章 Unity3D热更新之LuaFramework篇[01]--从零开始 中,我们了解了怎么获得一个可用的LuaFramework框架. 本篇将我会先介绍一下如何配置Lua开发环境,然后分析在 ...