`
java-mans
  • 浏览: 11416882 次
文章分类
社区版块
存档分类
最新评论

深入equals方法

 
阅读更多

深入equals方法

equals方法的重要性毋须多言,只要你想比较的两个对象不愿是同一对象,你就应该实现
equals方法,让对象用你认为相等的条件来进行比较.

下面的内容只是API的规范,没有什么太高深的意义,但我之所以最先把它列在这儿,是因为
这些规范在事实中并不是真正能保证得到实现.

1.对于任何引用类型, o.equals(o) == true成立.
2.如果 o.equals(o1) == true 成立,那么o1.equals(o)==true也一定要成立.
3.如果 o.equals(o1) == true 成立且 o.equals(o2) == true 成立,那么
o1.equals(o2) == true 也成立.
4.如果第一次调用o.equals(o1) == true成立再o和o1没有改变的情况下以后的任何次调用
都成立.
5.o.equals(null) == true 任何时间都不成立.

以上几条规则并不是最完整的表述,详细的请参见API文档.

对于Object类,它提供了一个最最严密的实现,那就是只有是同一对象是,equals方法才返回
true,也就是人们常说的引用比较而不是值比较.这个实现严密得已经没有什么实际的意义,
所以在具体子类(相对于Object来说)中,如果我们要进行对象的值比较,就必须实现自己的
equals方法.

先来看一下以下这段程序:


public boolean equals(Object obj)
{
if (obj == null) return false;
if (!(obj instanceof FieldPosition))
return false;
FieldPosition other = (FieldPosition) obj;
if (attribute == null) {
if (other.attribute != null) {
return false;
}
}
else if (!attribute.equals(other.attribute)) {
return false;
}
return (beginIndex == other.beginIndex
&& endIndex == other.endIndex
&& field == other.field);
}

这是JDK中java.text.FieldPosition的标准实现,似乎没有什么可说的.

我信相大多数或绝大多数程序员认为,这是正确的合法的equals实现.毕竟它是JDK的API实现啊.

还是让我们以事实来说话吧:

package debug;

import java.text.*;

public class Test {
public static void main(String[] args) {
FieldPosition fp = new FieldPosition(10);
FieldPosition fp1 = new MyTest(10);
System.out.println(fp.equals(fp1));
System.out.println(fp1.equals(fp));
}
}
class MyTest extends FieldPosition{
int x = 10;
public MyTest(int x){
super(x);
this.x = x;
}
public boolean equals(Object o){
if(o==null) return false;
if(!(o instanceof MyTest )) return false;
return ((MyTest)o).x == this.x;
}
}

运行一下看看会打印出什么:

System.out.println(fp.equals(fp1));打印true
System.out.println(fp1.equals(fp));打印flase

两个对象,出现了不对称的equals算法.问题出在哪里(脑筋急转弯:当然出在JDK实现的BUG)?

我相信有太多的程序员(除了那些根本不知道实现equals方法的程序员外)在实现equals方法
时都用过instanceof运行符来进行短路优化的,实事求是地说很长一段时间我也这么用过。
太多的教程,文档都给了我们这样的误导。而有些稍有了解的程序员可能知道这样的优化可能
有些不对但找不出问题的关键。另外一种极端是知道这个技术缺陷的骨灰级专家就提议不要这
样应用。

我们知道,"通常"要对两个对象进行比较,那么它们"应该"是同一类型。所以首先利用instanceof
运行符进行短路优化,如果被比较的对象不和当前对象是同一类型则不用比较返回false,但事实
上,"子类是父类的一个实例",所以如果 子类 o instanceof 父类,始终返回true,这时肯定
不会发生短路优化,下面的比较有可能出现多种情况,一种是不能造型成子类而抛出异常,另一种
是父类的private 成员没有被子类继承而不能进行比较,还有就是形成上面这种不对称比较。可能
会出现太多的情况。


那么,是不是就不能用 instanceof运行符来进行优化?答案是否定的,JDK中仍然有很多实现是正
确的,如果一个class是final的,明知它不可能有子类,为什么不用 instanceof来优化呢?

为了维护SUN的开发小组的声誉,我不说明哪个类中,但有一个小组成员在用这个方法优化时在后加
加上了加上了这样的注释:

if (this == obj) // quick check
return true;
if (!(obj instanceof XXXXClass)) // (1) same object?
return false;
可能是有些疑问,但不知道如何做(不知道为什么没有打电话给我......)

那么对于非final类,如何进行类型的quick check呢?

if(obj.getClass() != XXXClass.class) return false;

用被比较对象的class对象和当前对象的class比较,看起来是没有问题,但是,如果这个类的子类
没有重新实现equals方法,那么子类在比较的时候,obj.getClass() 肯定不等于XXXCalss.class,
也就是子类的equals将无效,所以if(obj.getClass() != this.getClass()) return false;才是正
确的比较。

注意if(obj.getClass() != this.getClass()) return false;只是为了让子类能正常工作,至于子类

继承后如果正确地比较,不是父类应该考虑的,那是子类实现者提供的,事实上在子类中只在先调用

super.equals就能先比较父类的私有变量,然后子类中再比较子类扩展的类成员。在父类中利用动态

反射访问子类的所有成员来比较,虽然可以达到让子类直接使用equals的目的,但这违反了设计原则。

你为未来考虑得太远。

另外一个quick check是if(this==obj) return true;


是否equals方法一定比较的两个对象就一定是要同一类型?上面我用了"通常",这也是绝大多数程序
员的愿望,但是有些特殊的情况,我们可以进行不同类型的比较,这并不违反规范。但这种特殊情况
是非常罕见的,一个不恰当的例子是,Integer类的equals可以和Sort做比较,比较它们的value是不
是同一数学值。(事实上JDK的API中并没有这样做,所以我才说是不恰当的例子)

在完成quick check以后,我们就要真正实现你认为的“相等”。对于如果实现对象相等,没有太高
的要求,比如你自己实现的“人”类,你可以认为只要name相同即认为它们是相等的,其它的sex,
ago都可以不考虑。这是不完全实现,但是如果是完全实现,即要求所有的属性都是相同的,那么如
何实现equals方法?

class Human{
private String name;
private int ago;
private String sex;
....................
public boolean equals(Object obj){
quick check.......
Human other = (Human)ojb;
return this.name.equals(other.name)
&& this.ago == ohter.ago
&& this.sex.equals(other.sex);
}
}

关于equals方法的最后一点是:如果你要是自己重写(正确说应该是履盖)了equals方法,那同时就一
定要重写hashCode().为是规范,否则.............
我们还是看一下这个例子:

public final class PhoneNumber {
private final int areaCode;
private final int exchange;
private final int extension;

public PhoneNumber(int areaCode, int exchange, int extension) {
rangeCheck(areaCode, 999, "area code");
rangeCheck(exchange, 99999999, "exchange");
rangeCheck(extension, 9999, "extension");
this.areaCode = areaCode;
this.exchange = exchange;
this.extension = extension;
}

private static void rangeCheck(int arg, int max, String name) {
if(arg < 0 || arg > max)
throw new IllegalArgumentException(name + ": " + arg);
}

public boolean equals(Object o) {
if(o == this)
return true;
if(!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber)o;
return pn.extension == extension && pn.exchange == exchange && pn.areaCode == areaCode;
}
}

注意这个类是final的,所以这个equals实现没有什么问题。

我们来测试一下:

public static void main(String[] args) {
Map hm = new HashMap();
PhoneNumber pn = new PhoneNumber(123, 38942, 230);
hm.put(pn, "I love you");
PhoneNumber pn1 = new PhoneNumber(123, 38942, 230);
System.out.println(pn);
System.out.println("pn.equals(pn1) is " + pn.equals(pn1));
System.out.println(hm.get(pn1));
System.out.println(hm.get(pn));
}
既然pn.equals(pn1),那么我put(pn,"I love you");后,get(pn1)这什么是null呢?
答案是因为它们的hashCode不一样,而hashMap就是以hashCode为主键的。

所以规范要求,如果两个对象进行equals比较时如果返回true,那么它们的hashcode要求返回相等的值。

好了,休息,休息一下。。。。。。。。。。。。。。。。

分享到:
评论

相关推荐

    Java equals 方法与hashcode 方法的深入解析.rar

    Java equals 方法与hashcode 方法的深入解析.rar

    Java语言深入_equals

    Java语言深入_equals

    深入理解equals和hashCode方法

    在Java中,equals和hashCode方法是Object中提供的两个方法,这两个方法对以后的学习有很大的帮助,本文就深度来去讲解这两个方法。下面小编带大家来一起学习吧

    Java equals 方法与hashcode 方法的深入解析

    PS:本文使用jdk1.7解析1.Object类 的equals 方法 代码如下: /** * Indicates whether some other object is “equal to” this one. *  * The {@code equals} method implements an equivalence relation * on ...

    Java解惑系列之一--equals和==之间究竟有什么区别

    equals和==的区别?...大家对于equals这个函数的理解不够深入,只凭上面的一句话,我们是无法了解equals这个函数的来龙去脉的。 第二我个人认为这个问题出的非常的愚蠢,所以忍不住拿出来调侃一下,呵呵。

    Java的Object类讲解案例代码 equals()、hashCode()、finalize()、clone()、wait()

    Object类是所有Java类的根类,它定义了一些常用的方法,例如equals()、hashCode()、toString()等。本案例代码将详细展示Object类的使用方法,并提供一些实际场景下的案例,以帮助开发者更好地理解和运用这些方法。 ...

    深入理解Java中HashCode方法

    主要介绍了深入理解Java中HashCode方法,具有一定借鉴价值,需要的朋友可以参考下

    java深入解析

    103 话题18 一成不变——不可修改的String对象 107 话题19 钩深索隐——String字符最大长度的探索 111 话题20 追本溯源——追寻String字面常量的“极限” 116 话题21 旧调重弹——再论equals方法与“==”的 区别 ...

    node-any-equals:等于比较任何数据类型

    任何等于深入比较任何JavaScript数据类型安装$ npm i --save any-equals用法var equals = require('./lib');equals( ['foo', 'bar'], ['bar', 'foo']); // =&gt; trueequals( {foo: [[{}], 'foo', 'bar']}, {foo: ['bar...

    基于Java字符串 &quot;==&quot; 与 &quot;equals&quot; 的深入理解

    本篇文章是对Java中的字符串"=="与"equals"进行了详细的分析介绍,需要的朋友参考下

    亮剑.NET深入体验与实战精要3

    2.1 Equals()和运算符==的区别 80 2.2 const和readonly的区别 82 2.3 private、protected、public和internal的区别 86 2.4 sealed、new、virtual、abstract与override 87 2.5 abstract class与interface 91 2.6 公共...

    面试day1-基础.docx

    这份面试题集涵盖了Java面试中常见的一些问题,从多继承的原因到equals方法的区别,再到包装类的应用场景和类型擦除的概念,覆盖了Java语言中的多个重要方面。这些问题不仅考察了基本的语法知识,还涉及到Java的特性...

    亮剑.NET:.NET深入体验与实战精要清晰版及源码

     2.1 equals()和运算符==的区别  2.2 const和readonly的区别  2.3 private、protected、public和internal的区别  2.4 sealed、new、virtual、abstract与override  2.5 abstract class与interface  2.6 ...

    亮剑.NET深入体验与实战精要2

    2.1 Equals()和运算符==的区别 80 2.2 const和readonly的区别 82 2.3 private、protected、public和internal的区别 86 2.4 sealed、new、virtual、abstract与override 87 2.5 abstract class与interface 91 2.6 公共...

    Java实例高难度面试题及解析 - 展现你的编程实力!

    此外,我们还探讨了对象的哈希码、重写equals()和hashCode()方法的技巧,以及对象的序列化和反序列化。 通过研究和解答这些高难度问题,您将提升自己的编程水平,展现出对Java实例概念和相关技术的深入理解。无论您...

    银行账户管理系统 简称BAM(项目介绍及源码)绝对精典

    为Account类及其子类添加toString方法和equals方法 项目四 练习7:(Exception) 为BAM添加几个异常类 BalanceNotEnoughException :用于取钱的时候余额不足的情况(包括账户余额超过透支额的情况) RegisterException:...

    Java面试题-基础和集合.docx

    其中,讨论了Java为什么不支持多继承、==和equals的区别、方法重载的条件、String为何设计成不可变、包装类的应用场景、Integer的高速缓存机制等多个方面。 通过面试题的逐一解答,读者可以了解到Java语言的一些...

    C#5.0本质论第四版(因文件较大传的是百度网盘地址)

    9.1.3 重写Equals() 256 9.2 操作符重载 263 9.2.1 比较操作符 264 9.2.2 二元操作符 265 9.2.3 赋值与二元操作符的结合 266 9.2.4 条件逻辑操作符 266 9.2.5 一元操作符 267 9.2.6 ...

    Java hashCode() 方法详细解读

    Java.lang.Object 有一个hashCode()和一个equals()方法,这两个方法在软件设计中扮演着举足轻重的角色,本文对hashCode()方法深入理解,希望能帮助大家

Global site tag (gtag.js) - Google Analytics