`
ywu
  • 浏览: 457439 次
  • 性别: Icon_minigender_1
  • 来自: 无锡
社区版块
存档分类
最新评论

equals

阅读更多

说明:

本系列博客是本人在工作中遇到的一些问题的整理,其中有些资料来源网络博客,有些信息来自出版的书籍,掺杂一些个人的猜想及验证,总结,主要目的是方便知识的查看,并非纯原创。本系列博客会不断更新。原创不容易,支持原创。对于参考的一些其他博客,会尽量把博客地址列在博客的后面,以方便知识的查看。

 

本篇博客可以看做是《Effective Java中文版2版》第三章(对于所有对象都通用的方法)第八条(覆盖equals时请遵守通用约定)的读书笔记,其中掺杂了一些个人的想法及验证。

 

equals方法定义在Object类中,以下是Object类中equals方法的定义:

public boolean equals(Object obj) {

    return (this == obj);

}

 

Object类中的equals方法默认比较的是对象的内存地址,只有内存地址相同的的两个对象才被认为是相同的;

 

什么时候需要覆盖Object类中的equals方法?

当一个类具有自己特有的"逻辑相等"概念(不同于对象等同概念, ==),而且父类还没有覆盖equals方法以实现期望的行为,这时就需要覆盖Object类中的equals方法。

 

"逻辑相等"是指对象在逻辑上,或者说在业务范围内是相等的,而不是指它们是否指向同一个对象。举个例子,比如说业务上规定,学生不能同名,那么表示学生实体的两个对象,name属性值是一样的话,就可以认为这两个对象是相同的;再比如,平面上的点,只要它们的横坐标和纵坐标分别相等,那么就可以认为这些点是相同的。

 

覆盖equals方法时必须遵守的通用约定:

1、自反性:对于任何非null的引用值x,x.equals(x)必须返回true

2、对称性:对于任何非null的引用值x,y,当且仅当y.equals(x)返回true,x.equals(y)必须返回true

3、传递性:对于任何非null的引用值x,y,z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true

4、一致性:对于任何非null的引用值x,y,只要equals的比较操作在对象中用到的信息没有被修改,多次调用x.equals(y)就会一致的返回true,或者一致的返回false

5、对于任何非null的引用值x,x.equals(null)必须返回false

一个类的实例会被频繁地传递给另一个类的实例,有许多类,包括所有的集合类,都依赖于传递给它们的对象是否遵守了equals约定。

 

我们来看下书中的一个例子:

public class CaseInsensitiveString {

    private String s = null;

    public CaseInsensitiveString(String s){

        if (null == s){

            throw new IllegalArgumentException();

        }

        this.s = s;

    }

    @Override

    public boolean equals(Object o) {

        if (o instanceof CaseInsensitiveString){

            return s.equalsIgnoreCase(((CaseInsensitiveString)o).s);

        }

        if (o instanceof String){

            return s.equalsIgnoreCase((String)o);

        }

        return false;

    }

}

 

这是一个大小写不敏感的String,其中覆盖了父类的equals方法,String类型的入参做了兼容,看下测试类:

public class CaseInsensitiveStringTest {

    public static void main(String[] args) {

        String s = "polish";

        CaseInsensitiveString cis = new CaseInsensitiveString("Polish");

 

        System.out.println("cis.equals(s):" + cis.equals(s));

        System.out.println("s.equals(cis):" + s.equals(cis));

 

        List<CaseInsensitiveString> list = new ArrayList<CaseInsensitiveString>();

        list.add(cis);

 

        System.out.println("list.contains(s):" + list.contains(s));

    }

}

控制台输出:


cis.equals(s):true因为对String类型的入参做了兼容,所以返回true

s.equals(cis):false由于String类的equals方法并没有对CaseInsensitiveString类型做兼容,类型不匹配,因此返回false;我们看下String类的equals方法:

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;

}

首先比较两个对象的引用是否相等,其次判断入参类型是否是StringString的子类,然后判断字符数是否相等,如果这些条件都相等,再依次比较字符序列中的每一个对应位置的字符是否相等,所有这些条件都满足才返回true,否则返回false,s.equals(cis)不满足anObject instanceof String,因此返回false

 

list.contains(s):false,我们来看下List的实现类ArrayList中的contains方法实现:

public boolean contains(Object o) {

    return indexOf(o) >= 0;

}

再看indexOf(Object o)方法:

public int indexOf(Object o) {

    if (o == null) {

        for (int i = 0; i < size; i++)

        if (elementData[i]==null)

            return i;

    } else {

        for (int i = 0; i < size; i++)

            if (o.equals(elementData[i]))

                return i;

        }

        return -1;

}

来分析一下,indexOf(Object o)方法入参s不为null,执行else分支,循环,判断o.equals(elementData[i]),这里的对象o实际是String类型,elementData[i]CaseInsensitiveString类型,由于String类的equals方法没有对CaseInsensitiveString做兼容,因此indexOf(Object o)方法最终返回-1,导致contains方法返回false,如果ArrayList中的indexOf方法中o.equals(elementData[i])反过来,elementData[i].equals(o),那么contains方法就会返回true

 

CaseInsensitiveStringequals方法实现违反了对称性约束,list.contains(s)返回结果取决于具体的实现。

 

再看书中的另一个例子,父类与子类的equals问题:

/**

 * Created with IntelliJ IDEA.

 * User: yejunwu123@gmail.com

 * Date: 2014-08-19 16:24

 * Description:

 */

public class Point {

    private int x;

    private 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 this.x == p.x && this.y == p.y;

    }

}

 

/**

 * Created with IntelliJ IDEA.

 * User: yejunwu123@gmail.com

 * Date: 2014-08-19 16:28

 * Description:

 */

public class ColorPoint extends Point {

    private 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 ColorPoint)){

            return false;

        }

        return super.equals(obj) && this.color == ((ColorPoint)obj).color;

    }

}

 

/**

 * Created with IntelliJ IDEA.

 * User: yejunwu123@gmail.com

 * Date: 2014-08-19 16:30

 * Description:

 */

public enum Color {

    RED,BLUE

}

 

看下测试类:

/**

 * Created with IntelliJ IDEA.

 * User: yejunwu123@gmail.com

 * Date: 2014-08-19 16:36

 * Description:

 */

public class PointTest {

    public static void main(String[] args) {

        Point point = new Point(1,2);

        ColorPoint colorPoint = new ColorPoint(1,2,Color.RED);

        System.out.println("point.equals(colorPoint):" + point.equals(colorPoint));

        System.out.println("colorPoint.equals(point):" + colorPoint.equals(point));

    }

}

控制台输出:

point.equals(colorPoint):true

colorPoint.equals(point):false

这个不难理解,ColorPoint 类的equals方法中判断入参类型obj instanceof ColorPoint返回false,因此colorPoint.equals(point)返回false关于instanceof的一些说明可以参看博客http://ywu.iteye.com/blog/2105750

 

ColorPoint 类的equals修改:

@Override

public boolean equals(Object obj) {

    if (!(obj instanceof Point)){

        return false;

    }

    if (!(obj instanceof ColorPoint)){

        //obj is a Point

        return obj.equals(this);

    }

    //obj is a ColorPoint

    return super.equals(obj) && this.color == ((ColorPoint)obj).color;

}

再运行下测试:

public class PointTest {

    public static void main(String[] args) {

        Point point = new Point(1,2);

        ColorPoint colorPoint = new ColorPoint(1,2,Color.RED);

        System.out.println("point.equals(colorPoint):" + point.equals(colorPoint));

        System.out.println("colorPoint.equals(point):" + colorPoint.equals(point));

    }

}

 

控制台输出:

point.equals(colorPoint):true

colorPoint.equals(point):true

这个结果不难分析出来

 

修改下测试类:

ColorPoint cp1 = new ColorPoint(1,2,Color.RED);

Point p2 = new Point(1,2);

ColorPoint cp3 = new ColorPoint(1,2,Color.BLUE);

System.out.println("cp1.equals(p2):" + cp1.equals(p2));

System.out.println("p2.equals(cp3):" + p2.equals(cp3));

System.out.println("cp1.equals(cp3):" + cp1.equals(cp3));

 

不难分析出,当两个类型都为ColorPoint ,在判断this.color == ((ColorPoint)obj).color时为false,因此

cp1.equals(cp3)false

 

这种情形就违背了传递性,因此在子类继承父类,覆盖equals时需要注意。

 

Effective Java中文版2版》一书中说,我们无法在扩展可实例化类的同时,既增加值组件,又保留equals约定,除非愿意放弃面向对象抽象所带来的优势。对于这种情形,书中提出了复合优于继承的策略,有兴趣的可以看下书。

 

可以在一个抽象类的子类中添加值组件,而不会违反equals约定。

 

Effective Java中文版2版》中的建议:

1、使用==操作符检查 "参数是否为这个对象的引用"。这项操作不是必须的,因为在进行类型判断时null instanceof 任何类型将返回false,这种方式是作为一种性能优化方式,如果比较操作比较昂贵的话;

2、使用instanceof操作符检查 "参数是否为正确的类型",正确的类型一般是指equals方法所在的那个类;

3、把参数转换成正确的类型;

4、对于类中的每个"关键域",检查参数中的域是否与该对象中对应的域相匹配,对于floatdouble以外的基本类型,可以使用==比较,引用类型可以递归的调用equals方法,float类型可以调用Float.compare方法,double类型可以调用Double.compare方法比较,对于数组类型,需要把以上规则应用到每个元素,如果数组中的每个元素都很重要,可以使用Arrays.equals方法。

如果这些测试都成功,则返回true,否则返回false

 

域的比较顺序可能会影响到equals方法的性能,应该比较最有可能不一致的域,或开销最低的域。

 

覆盖equals方法时总要覆盖hashCode方法,关于hashCode,参看http://ywu.iteye.com/blog/2106466

  • 大小: 2.7 KB
分享到:
评论
1 楼 wyz_jiang 2016-01-28  
这波比我给82分,剩下的18分,我打666

相关推荐

    java中equals和==的区别

    Java 中 equals 和 == 的区别 Java 中的 equals 和 == 是两个不同的概念,很多开发者容易混淆它们。理解这两个概念的区别是非常重要的,因为它们对编程的正确性和性能都有很大的影响。 首先,我们需要了解 Java ...

    equals方法的重写.docx

    ### equals方法重写知识点解析 #### 一、equals方法简介 `equals`方法是Java语言中Object类的一个重要成员方法,其默认实现是比较两个对象的内存地址是否相同(即是否为同一个对象)。为了使对象之间能够基于内容...

    C#使用Equals()方法比较两个对象是否相等的方法

    在C#编程语言中,`Equals()`方法是一个用于比较对象是否相等的关键工具。这个方法在处理对象间的等价性判断时非常常见,特别是在需要确定两个变量或实例是否表示相同数据的情况下。`Equals()`方法是Object类的一个...

    ==和equals的区别

    Java 中的 == 和 equals 方法的区别 在 Java 中,比较值大小有两种方法:== 和 equals,这两个方法的使用场景和比较规则不同,下面我们将详细探讨它们的区别。 基本数据类型和引用数据类型 在 Java 中,有两种...

    Java中equals方法隐藏的陷阱

    ### Java中equals方法隐藏的陷阱 在Java编程中,正确实现`equals`方法至关重要,它不仅影响对象的比较逻辑,还直接关系到集合类(如`HashSet`、`HashMap`等)的行为。本文将深入探讨Java中`equals`方法的一些常见...

    Java理论与实践:hashCode()和equals()方法

    本文还介绍了定义对象的相等性、实施equals()和hashCode()的需求、编写自己的equals()和hashCode()方法。通过统一定义equals()和hashCode(),可以提升类作为基于散列的集合中的关键字的使用性。

    Java中的==和equals区别

    ### Java中的`==`与`equals`方法的区别详解 在Java编程中,比较对象的相等性是一个常见的需求,但很多初学者对于`==`运算符与`equals`方法的区别容易混淆。本文将深入探讨两者之间的差异,以及它们在不同场景下的...

    知识点 比较运算符==和equals方法的比较

    在Java编程语言中,比较运算符`==`和`equals()`方法是用来检查两个对象是否相等的,但它们之间存在显著的区别。理解这些差异对于编写正确的代码至关重要。 首先,我们来看`==`运算符。它主要用于基本数据类型的比较...

    java 资料 equals 与== 的区别

    Java 中的equals和==的区别 Java 是一种面向对象的编程语言,其中有两种数据类型:基本数据类型和引用数据类型。基本数据类型包括整数类型、浮点数类型、字符类型、布尔类型等,共有八种;而引用数据类型则包括 ...

    winform 重写Equals源码

    `Equals`方法是.NET类库中的一个基本成员,主要用于比较对象是否相等。在某些情况下,尤其是自定义控件或者业务实体类的设计中,我们可能需要重写`Equals`方法来实现特定的比较逻辑。下面我们将深入探讨WinForm中...

    java中equals()函数的用法 equals和==的区别

    在Java编程语言中,`equals()`方法和`==`运算符是两个经常被用来比较对象是否相等的关键概念。理解它们的区别对于编写出正确、健壮的代码至关重要。 首先,`==`运算符用于基本类型(如int, char, boolean)的比较,...

    HashCode相同equals不同的2位字符集合算法

    在Java编程语言中,`hashCode()` 和 `equals()` 是两个非常重要的方法,它们主要用于对象的比较和哈希表(如HashMap)的操作。标题提到的"HashCode相同equals不同的2位字符集合算法"涉及到的是一个特定场景:两个...

    equals问题经典

    在Java编程语言中,`equals()`方法是用来比较两个对象的内容是否相等的,而`==`操作符则是用来比较两个变量是否引用同一个对象。这里我们将深入探讨`equals()`方法的用法,以及它与`==`的区别,同时解决题目中提出的...

    set接口经常用的hashCode和equals方法详解

    ### set接口中hashCode和equals方法详解 #### 一、引言 在Java编程语言中,`Set`接口作为集合框架的重要组成部分,在实现无重复元素的数据结构方面扮演着关键角色。为了确保元素的唯一性,`Set`接口依赖于对象的`...

    ==和equals的比较

    C# 中的 == 和 equals 比较 在 C# 编程语言中,`==` 和 `equals` 是两个常用的比较运算符,但它们之间有很大的区别。 堆和栈的区别 在理解 `==` 和 `equals` 之前,我们需要了解堆和栈的区别。堆是一种动态分配的...

    equals与==之间的区别

    在Java编程语言中,`equals`方法与`==`操作符是进行对象比较时最常用的两种方式,但它们之间存在着显著的区别。理解这些差异对于正确地处理对象比较至关重要。 ### `==` 操作符 `==`操作符主要用于比较两个基本...

    hashcode和equals方法

    equals()和hashcode()这两个方法都是从object类中继承过来的。当String 、Math、还有Integer、Double。。。。等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法.

    ==运算符和Equals()方法区别

    "运算符和Equals()方法区别" 在C#语言中,`==`运算符和`Equals()`方法都是用来比较两个对象是否相等,但是它们之间存在着很大的区别。 对于值类型来说,`==`运算符和`Equals()`方法的行为是一致的,都会比较两个...

    重写toString和equals方法

    Java 对象的toString和equals方法重写 在 Java 中,每个对象都继承自 Object 类,而 Object 类中定义了两个重要的方法:toString() 和 equals()。这两个方法都是非常重要的,它们分别用于对象的字符串表示和对象...

    java中的==和equals()方法1

    在Java编程语言中,了解如何正确使用`==`和`equals()`方法是非常关键的,因为它们在比较对象和基本类型时有不同的行为。下面将详细解释这两个方法的工作原理、使用场景以及一些常见误区。 首先,`==`运算符主要用于...

Global site tag (gtag.js) - Google Analytics