本文涉及到的概念
1.为什么重载equals方法时,要遵守通用约定
2.重载equals方法时,要遵守哪些通用约定
 
为什么重载equals方法时,要遵守通用约定
Object类的非final方法都有明确的通用约定,这些方法是被设计成被重载的。重载时,如果不遵守通用约定,那么,其它依赖于这些通用约定的类(例如HashMap和HashSet)就无法结合该类一起正常工作----<<effective java>>
 
 
quals方法实现了等价关系,重载时要遵守的通用约定:
a.自反性(reflexive)  对于任何非null的引用值x, x.equals(x)必须返回true。
b.对称性(symmetric)  对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true
c.传递性(transitive)   对于任何非null的引用值x,y和z,如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)返回true
d.一致性      对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致地返回

true,或者一致地返回false
e.对于任何非null的引用值x,x.equals(null)必须返回false
 
a.自反性
基本上不会违背这一条规定。如果违背了的话,将一个引用添加到一个集合中,然后,调用集合的contains(x)方法,它会返回false。x.equals(x)不等于true,导致contains(x)方法返回false。
 
b.对称性
对于任何非null的引用值x和y, x.equals(y)返回true, y.equals(x)也要返回true
public final class CaseInsensitiveString {
private final String s; public CaseInsensitiveString(String s) {
if (s == null)
throw new NullPointerException();
this.s = s;
} // Broken - violates symmetry!
@Override
public boolean equals(Object o) {
if (o instanceof CaseInsensitiveString)
return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
if (o instanceof String) // One-way interoperability!
return s.equalsIgnoreCase((String) o);
return false;
} // This version is correct.
// @Override public boolean equals(Object o) {
// return o instanceof CaseInsensitiveString &&
// ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
// } public static void main(String[] args) {
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
System.out.println(cis.equals(s) + " " + s.equals(cis));
}
}
可以把上述的例子代码,代入对称性公式,CaseInsensitivesString为x, String为y, CaseInsensitivesString为y.
x.equals(y),y.equals(x)都为true,当y是CaseInsensitivesString类型时;当y为String类型时,y.equals(x),就为false。
 
String类的equals方法的实现: stringInstance.equals(CanseInsensitivesStringIns),它会返回false,因为x不是String类型
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
c.传递性
equals预定的第三个要求是,如果一个对象等于第二个对象,并且第二个对象又等于第三个对象,则第一个对象一定等于第三个对象。
----<<effective java>>
重写类的equals方法后,使用x.equals(y), y.equals(z),x.equals(z),去检验。如果发现不符合,则违反了该通用约定,那么,就要重新实现。
 
下面的例子,违反了传递性:
父类Point,Point类之间的比较,重写equals方法,比较内容是否相等,不是比较引用地址是否相等。
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 o) {
if (!(o instanceof Point))
return false;
Point p = (Point) o;
return p.x == x && p.y == y;
} // Broken - violates Liskov substitution principle - Pages 39-40
// @Override public boolean equals(Object o) {
// if (o == null || o.getClass() != getClass())
// return false;
// Point p = (Point) o;
// return p.x == x && p.y == y;
// } // See Item 9
@Override
public int hashCode() {
return 31 * x + y;
}
}

创建一个子类,继承Point类

import java.awt.Color;

public class ColorPoint extends Point {
private final Color color; public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
} // Broken - violates symmetry!
//x为Point,y为ColorPoint
// x.equals(y)为true,但是y.equals(x)为false
@Override
public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
return super.equals(o) && ((ColorPoint) o).color == color;
} // Broken - violates transitivity!
// @Override public boolean equals(Object o) {
// if (!(o instanceof Point))
// return false;
//
// // If o is a normal Point, do a color-blind comparison
// if (!(o instanceof ColorPoint))
// return o.equals(this);
//
// // o is a ColorPoint; do a full comparison
// return super.equals(o) && ((ColorPoint)o).color == color;
// }
//p1.euqlas(p2),p2.equals(p3),p1.equals(p3)
//输出结果:输出结果 true, true ,false public static void main(String[] args) {
// First equals function violates symmetry
Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);
System.out.println(p.equals(cp) + " " + cp.equals(p)); // Second equals function violates transitivity
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
System.out.printf("%s %s %s%n", p1.equals(p2), p2.equals(p3), p1.equals(p3));
}
}
p1和p2的比较,不考虑颜色;p2和p3的比较,也不考虑颜色;p1和p3的比较考虑颜色,于是p1和p3不相等。违反了传递性原则,x.equals(y),y.equals(z).x.equals(z)
 
如何解决这个问题?
事实上,这是面向对象语言中关于等价关系的一个基本问题。我们无法在扩展可实例化的类的同时,既增加新的值组件,同时又保留equals约定,除非愿意放弃面向对象的抽象所带来的优势。
也就是,我们无法在扩展父类,然后,同时在子类中保留父类和子类的equals约定。
 
解决办法:
a.复合优先于继承
b.父类是抽象类(没有任何值组件),子类添加值域
 
使用复合的方式来解决

// Simple immutable two-dimensional integer point class - Page 37

import java.util.*;

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 o) {
if (!(o instanceof Point))
return false;
Point p = (Point)o;
return p.x == x && p.y == y;
} // See Item 9
@Override public int hashCode() {
return 31 * x + y;
}
}
public enum Color { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET } // Adds a value component without violating the equals contract - Page 40
public class ColorPoint {
private final Point point;
private final Color color; public ColorPoint(int x, int y, Color color) {
if (color == null)
throw new NullPointerException();
point = new Point(x, y);
this.color = color;
} /**
* Returns the point-view of this color point.
*/
public Point asPoint() {
return point;
} @Override public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint) o;
return cp.point.equals(point) && cp.color.equals(color);
} @Override public int hashCode() {
return point.hashCode() * 33 + color.hashCode();
}
} /////////////////////////////////////////////////////////////////////////
public class Test { public static void main(String[] args) {
// First equals function violates symmetry
Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);
System.out.println(p.equals(cp) + " " + cp.equals(p)); // Second equals function violates transitivity
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
System.out.printf("%s %s %s%n", p1.equals(p2), p2.equals(p3), p1.equals(p3));
}
}
输出结果:
false false
false false false
 

重载equals方法时要遵守的通用约定--自反性,对称性,传递性,一致性,非空性的更多相关文章

  1. 第八条——覆盖equals方法时需遵守的通用约定

    1)自反性 对于任何非null的引用值x,x.equals(x)必须返回true.---这一点基本上不会有啥问题 2)对称性 对于任何非null的引用值x和y,当且仅当x.equals(y)为true ...

  2. 覆盖equals方法时请遵守通用约定

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

  3. Effective Java 第三版——10. 重写equals方法时遵守通用约定

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  4. Effective Java 第三版——11. 重写equals方法时同时也要重写hashcode方法

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  5. [改善Java代码]覆写equals方法时不要识别不出自己

    建议45: 覆写equals方法时不要识别不出自己 我们在写一个JavaBean时,经常会覆写equals方法,其目的是根据业务规则判断两个对象是否相等,比如我们写一个Person类,然后根据姓名判断 ...

  6. 使用equals方法时,要注意

    这是我在项目中犯的一个低级错误: 使用equals方法时,要注意这个方法是boolean java.lang.String.equals(Object anObject)传递的是Object,所以传任 ...

  7. 第九条:覆盖equals方法时总要覆盖hashCode方法

    Object类的hashCode方法: public native int hashCode();   是一个本地方法. 其中这个方法的主要注释如下: Whenever it is invoked o ...

  8. 【Java实战】源码解析为什么覆盖equals方法时总要覆盖hashCode方法

    1.背景知识 本文代码基于jdk1.8分析,<Java编程思想>中有如下描述: 另外再看下Object.java对hashCode()方法的说明: /** * Returns a hash ...

  9. 为什么重写equals方法时,要求必须重写hashCode方法?

    1 equals方法 Object类中默认的实现方式是  :   return this == obj  .那就是说,只有this 和 obj引用同一个对象,才会返回true. 而我们往往需要用equ ...

随机推荐

  1. centos单机安装zookeeper+kafaka

    环境如下: CentOS-7-x86_64zookeeper-3.4.11kafka_2.12-1.1.0 一.zookeeper下载与安装1)下载zookeeper [root@localhost ...

  2. assert后面如果是假则程序崩溃

    assert后面如果是假,则程序崩溃.

  3. Python的字典和JSON

    Python的字典和JSON在表现形式上非常相似 #这是Python中的一个字典 dic = { 'str': 'this is a string', 'list': [1, 2, 'a', 'b'] ...

  4. 定义一个方法get_page(url),url参数是需要获取网页内容的网址,返回网页的内容。提示(可以了解python的urllib模块)

    定义一个方法get_page(url),url参数是需要获取网页内容的网址,返回网页的内容.提示(可以了解python的urllib模块) import urllib.request def get_ ...

  5. angularjs中的几种工具方法

    1.比较两个字符串是否相等 2.对象形式转化成json和json转化成字符串形式 3.便利对象遍历数组 4.绑定数据 5.多个app功能模块的实现 <!doctype html><h ...

  6. [LeetCode] Maximum Average Subarray I 子数组的最大平均值

    Given an array consisting of n integers, find the contiguous subarray of given length k that has the ...

  7. chall.tasteless.eu 中的注入题

    第一题好像就很难,看了payload,算是涨见识了,感觉有点为了猜而猜. 题目给我们的时候是这样的:http://chall.tasteless.eu/level1/index.php?dir=ASC ...

  8. github学习(三)

    Git学习(二) 分支学习: 创建新分支dev:git branch dev 切换到dev分支:git checkout dev 可以简写为一句话:git checkout -b dev 可以用命令g ...

  9. codevs 搜索题汇总(钻石+大师级)

    1043 方格取数 2000年NOIP全国联赛提高组  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 钻石 Diamond 题目描述 Description 设有N*N的方格图 ...

  10. LruCache的缓存策略

    一.Android中的缓存策略 一般来说,缓存策略主要包含缓存的添加.获取和删除这三类操作.如何添加和获取缓存这个比较好理解,那么为什么还要删除缓存呢?这是因为不管是内存缓存还是硬盘缓存,它们的缓存大 ...