第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. 【sql注入】浅谈JSP安全开发之SQL注入

    [sql注入]浅谈JSP安全开发之SQL注入 本文转自:i春秋社区 前言不管是用什么语言编写WEB应用程序,他们都或多或少有一些地方存在漏洞.如果你想知道漏洞的运行原理,和防御方案,那么请看完本篇文章 ...

  2. 吴恩达机器学习笔记13-正规方程(Normal Equation)

    到目前为止,我们都在使用梯度下降算法,但是对于某些线性回归问题,正规方程方法是更好的解决方案.如: 即: 运用正规方程方法求解参数: 注:对于那些不可逆的矩阵(通常是因为特征之间不独立,如同时包含英尺 ...

  3. CentOS搭建FTP服务

    前言: 环境:centos7.5 64 位 正文: 使用 yum 安装 vsftpd yum install vsftpd -y 安装完成后,启动 FTP 服务: service vsftpd sta ...

  4. SpringMVC框架五:图片上传与JSON交互

    在正式图片上传之前,先处理一个细节问题: 每一次发布项目,Tomcat都会重新解压war包,之前上传过的图片会丢失 为了解决这个问题:可以不在Tomcat下保存图片,而是另找一个目录. 上传图片: & ...

  5. Kali学习笔记4:DNS信息收集

    DNS记录 A记录 A记录是用来创建到IP地址的记录. A记录设置技巧 1.如果想创建不带www的记录,即cnblog.com,在主机记录中填写@或者留空,不同的注册商可能不一样. 2.创建多个域名到 ...

  6. Spark基础-scala学习(四、函数式编程)

    函数式编程 将函数赋值给变量 匿名函数 高阶函数 高级函数的类型推断 scala的常用高阶函数 闭包 sam转换 currying函数 return 将函数赋值给变量 scala中的函数是一等公民,可 ...

  7. 网页的异步请求(Ajax)

    JS原生Ajax操作(XMLHttpRequest) GET请求 var xmld=new XMLHttpRequest(); xmld.open("GET","wan. ...

  8. 通过session的id号获取对应的session

    说说为什么要用session!!! 每次访问端通过普通http协议访问tomcat时,访问端包括网页或Android app等,tomcat都会自动生成一个不同的session,而且session的i ...

  9. CentOS7+Mono5.2.0.224 +Jexus5.8.3.0 布署 Asp.Net MVC (vs2017)

    背景: 比起大神我们只是差远了,只知道一味的找找看,找的资料不少,但真不知道哪一个是正确的. 之前一个文章也写了怎么安装 Jexus 但始终只有是html的静态页面可以asp.net 都不行(http ...

  10. Redis主从+KeepAlived实现高可用

    Redis是我们当下比较流行使用的非关系数据库,可支持多样化的数据类型,多线程高并发支持,redis运行在内存拥有更快的读写.因为redis的表现如此出色,如何能保障redis在运行中能够应对宕机故障 ...