聊聊Java中的 ” == “、equals以及hashCode

前段时间一直在工作中使用Java,由于有一些C++功底,于是简单看了一下Java相关的语法便开始编写代码,结果在创建一个自定义类,并将自定义类放入ArrayList中,之后查找ArrayList是否有此元素的时候,发现怎么也查询不到对应的元素。在网上搜了一下资料,发现原因是没有重写对象的equals()方法,导致无法查找到对应的对象。之后由查了与之联系的相关资料,便有了以下的总结。

关于 “ == ”

“ ==
”操作符主要比较的是操作符两端对象的内存地址。如果两个对象的内存地址是一致的,那么就返回true。反之,则返回false。

话不多说,先来看看下面这段代码:

程序1-1

再来看看会输出什么:

上面程序的输出结果

怎么样,这个结果你想到了吗?

以下是关于上面结果的解释:

其实在Java中对于字符串的创建,有两种形式。

  • 一种是形如String a = “Hello”这样的字面量形式;
  • 另一种是形如String d = new String(“Hello”)这样的构造对象的方法。

这篇总结的形式是提出个问题,然后给出问题的答案。这是目前学习知识的一种尝试,可以让学习更有目的。

字面量形式

在JVM中,为了减少对字符串变量的重复创建,其拥有一段特殊的内存空间,这段内存被称为字符串常量池(constant
pool)
或者是“字符串字面量池”

程序1-1中,我们首先使用 String a = “Hello”
创建了一个对象。此时默认字符串常量池中没有内容为“Hello”的对象存在。当JVM在字符串常量池中没有找到内容为”Hello”的对象时,就会创建一个内容为”Hello”的对象,并将它的引用返回给变量a。

那么当我们在后面使用 String b = “Hello” 的时候,会发生什么情况呢?
首先JVM会在字符串常量池中查询是否有内容为“Hello”的对象。因为此时字符串常量池中已经有内容为“Hello”的对象了,故JVM不会创建新的地址空间,而是将原有的“Hello”对象的引用返回给b——所以此时变量a和变量b拥有的是同一个对象引用,即指向的是同一块内存地址,因此使用
== 操作符对a和b进行比较,结果为true。

Q1.什么时候应当重写对象的equals方法?

答:一般在我们需要进行值比较的时候,是需要重写对象的equals方法的。而例外情况在《effective
java》的第7条“在改写equals的时候请遵守通用约定”中清楚描述了。

我们知道,在Java中,每个对象都继承于Object.如果不重写,则默认的equals代码如下所示:

public boolean euqals(Object obj){
    return this == obj;
}

由上面的代码可以看出,equal默认是使用“==”来判断两个对象是否相等。两个对象使用“==”比较的是对象的地址,只有两个引用指向的对象相同的时候,“==”才返回true。所以,在开头的例子中,就需要重写equals方法,让两个对象有equals的时候。

使用new来构造一个对象

然而当我们new一个字符串对象时,不管字符串常量池中是否有内容相同的对象,JVM都会创造一个新的对象,所以地址肯定不一样,因此使用
== 操作符对a和d进行比较,结果为false。

Q2.如何重写equals?

答:首先,当改写equals方法时,需要保证满足它的通用约定。这些约定如下所示:

  • 自反性,对于任意的引用值x,x.equals(x)一定为true。
  • 对称性,对于任意的引用值x和y,当且仅当y.equals(x)时,x.equals(y)也一定返回true.
  • 传递性,对于任意的引用值x,y,z。如果x.equals(y)返回true,y.euqals(z)返回true,则x.equals(z)也返回true。
  • 一致性,对于任意的引用值x和y,如果用于equals比较的对象信息没有修改,那么,多次调用x.equals(y)要么一致返回true,要么一致返回false.
  • 非空性,所有的对象都必须不等于null。

其实我觉的一个简单的方法是参照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;
}

函数的解释如下所示:

  1. 使用==检查“实参是否是指向对象的一个引用”。
  2. 澳门新葡亰手机版,使用instanceof检查实参是否和本对象同类,如果不同类,就不相等。
  3. 将实参转换为正确的类型。
  4. 根据类的定义,检查实现此对象值相等的各个条件。

更详细的信息,还是请看《effective
java》的第7条“在改写equals的时候请遵守通用约定”。

关于equals

在Object类中,equals方法源码如下:

Object类中的equals方法

我们可以看到,在Object类中的equals方法其实在内部也是使用了“ ==
”操作符—–如果两个对象地址一样则返回true,反之则返回false。
既然equals方法里面也是使用“ ==
”,那为什么还要设立一个equals方法,而不是直接用“ == ”操作符呢?

别急,这只是Object类里面的equals方法,其实绝大部分Object的子类都对equals方法进行了改写:

String类中的equals方法

HashMap中的equals方法

除了会比较内存地址,以上两个类中的equals方法还会比较对象所包含的内容。如果两个对象所包含的内容相同,equals方法也是返回true。

官方文档中对equals方法的描述:

equals方法在非空对象引用上实现相等关系:

  • 自反性:对于任何非空引用值x,x.equals(x)都应返回true。
  • 对称性:对于任何非空引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)才应返回true。
  • 传递性:对于任何非空引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)应返回true。
  • 一致性:对于任何非空引用值x和y,多次调用x.equals(y)始终返回true
    或始终返回false,前提是对象上equals比较中所用的信息没有被修改。对于任何非空引用值x,x.equals(null)都应返回false。
    Object类的equals方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值x和y,当且仅当x和y引用同一个对象时,此方法才返回true(x
    == y具有值true)。
    注意:当此方法被重写时,通常有必要重写hashCode方法,以维护hashCode方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

其实在很多时候,我们都需要改写equals方法以适应我们的实际情况:

下面是一个Person类,包含name和age两个属性。

Person类

现在我们创建两个Person对象,但是name和age分别都设置一样的值,同时使用equals方法对这两个对象进行比较:

创建两个Person对象并用equals方法对其进行比较

比较之后的结果

虽然这从JVM的角度来看这个程序是对的,可是结果并不是我们想要的:对象one和对象two虽然是两个不同的对象,但是它们包含的元素的值是相同的,也就是说one和two应该都表示的是同一个人—–name
为 EakonZhao,年龄为19。可是为什么调用equals方法之后输出的却是false呢?

原因:由于我们没有对equals方法进行改写,所以当我们在调用equals方法的时候实际上调用的是Object类的equals方法。从前面我们可以得知,Object的equals方法在内部是直接使用“
== ”操作符对对象进行比较,这样当然会返回false啦!

下面我将对equals方法进行改写,以满足我们的需求;

改写之后的equals方法

改写equals方法之后的输出结果

对于改写equals方法之后是否需要改写hashCode方法以维护hashCode方法的常规协定,我在介绍完hashCode方法之后会继续讲