转载

没想到重写Java的equals()方法有这么多讲究

新爷的地盘(xin-zone):关注JAVA基础编程及大数据,注重经验分享及个人成长。

Javaequals 方法前需要先说说操作符 == ,因为很多人都困惑,操作符 ==equals 方法的区别。

Java 操作符 `==`

在Java的世界里,操作符 == 作用在基本数据类型( int short byte long float double boolean )上时,比较的是逻辑相等,作用在对象上时则比较则是对象的内存地址。

其实操作符 == 对于基本数据类型比较的也是地址,只不过 JVM 帮我们做了一些操作,让它使用起来是逻辑相等而已。下面我们就来说说具体原因。

基本类型的定义都是发生在 JVM 的栈内存中的。

比如我们定义一个变量 int i = 7 , JVM 将会在栈区划分一个区域存储 7 ,然后把地址赋给变量 i 。这样通过变量 i 就能找到 7

假设这个时候我们又定义了一个变量 int j = 7 , JVM 在内存中发现已经有 7 这个值了;这个时候就直接把变量 j 指向7;也就是变量 i 和变量 j 内存地址是一致的。所以当我们操作 i == j 返回的是真。

在假设,这个时候我们又定义了 i = 8 ,编译器将会重新划分一个区域存储 8 ,因为 JVM 发现内存中没有 8 这个值,然后将变量 i 的地址指向 8 ,而变量 j 的指向不会变。

Java equals() 方法

上面说了 Java 操作符 == 比较的是对象的内存地址。也就是验证对象的唯一性,只有两个对象是同一个对象的时候,他们的内存地址才是相同的。可以这么说,只有两个人是同一个人(好别扭)他的基因信息才完全一致,即便是双胞胎,基因信息也会有所不同。

但是我们日常开发中,大部分场景是想验证两个对象是否是逻辑相等,而不是想判断是否是同一个对象。这个时候我们可以重写基类 Objectequals() 方法。

Java 里面所有的类都继承自 Object 。所以每个类都包含 eqauls() 方法。 Object 中的 equals() 方法是这样定义的

1 public boolean equals(Object obj) {
2        return (this == obj);
3  }

从代码可以看出来比较的还是对象内存地址。所以如何重写 eqauls() 方法,就变得特别重要,因为一不小心就会造成很严重的错误。

还是那句话,除非特别必要而且很明确的需要 逻辑相等 ,否则不要重写 equals 方法。

重写equals

重写 equals ,应当遵守 Java SE 的通用约定。

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

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

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

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

对于任何非 null 的引用值 xx.equals(null) 必须返回 false

如果你违反了上面的约定,你的程序也许就会变得不那么正常。因为所有的集合类都依赖于传递给他的对象是否遵守 equals 约定。

举个例子

根据以上原则我们尝试重写一个自定义的 Java 类的 equals 方法。假定我们定一个 Book 类,希望该类实现逻辑相等。所以我们需要重写 equals 方法。

我们假设只要书名和出版社一致就认为是同一本书。

 1public class Book {
 2    private String name;
 3    private String publish;
 4    public Book(String name, String publish){
 5        this.name = name;
 6        this.publish = publish;
 7    }
 8    @Override
 9    public boolean equals(Object obj){
10
11        //使用 == 检查参数是否为这个对象的引用。
12        if(obj == this) return true;
13        //使用instanceof 检查参数是否为正确类型。
14        if (!(obj instanceof Book)) return false;
15
16        //把参数转化成正确类型
17        Book book = (Book) obj;
18
19        //对于该类中的关键字段,检查参数中的字段是否与该对象中对应的字段匹配。
20        return Objects.equals(name,book.name) &&
21                Objects.equals(publish,book.publish);
22    }
23
24}

这样通过 equals 方法就可以比较两本书是否是同一本书了,注意我代码中注释部分内容,可以说是重写 equals 的一些通用技巧。

hashCode

本来这篇文章写到这里我就想结束了,可是 hashCode 它不让我停 。因为 重写equals方法一定要重写hashCode方法。

如果不遵守上面这条规则,会给我们的程序带来意想不到的结果,枯燥的概念后面我会举例说明。

关于重写 hashCode 方法, JavaSE 一样给出了约定,如下:

  1. 在应用程序的执行期间,只要对象的 equals 方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次 hashCode 方法,它必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,这个整数可以不同。

  2. 如果两个对象根据 equals(Object) 方法是相等的,那么调用这两个对象中任一个对象的 hashCode 方法必须产生同样的整数结果。

  3. 如果两个对象根据 equals(Object) 方法是不相等的,那么调用这两个对象中任一个对象的 hashCode 方法,不要求必须产生不同的整数结果。然而,程序员应该意识到这样的事实,对于不相等的对象产生截然不同的整数结果,有可能提高散列表( hash table )的性能。

如果只重写了 equals 方法而没有重写 hashCode 方法的话,则会违反约定的第二条,即相等的对象必须具有相等的散列码 hashCode

两个逻辑相等的对象,如果产生不同的 hashCode 将会给 HashMap 等容器带来意想不到的结果。

再举个例子:

 1public class BookStore {
 2
 3    public static void main(String[] args){
 4
 5        HashMap<Book,String> bookStore = new HashMap<Book,String>();
 6        Book book1 = new Book("java","frank publish");
 7        Book book2 = new Book("java","frank publish");
 8        bookStore.put(book1,"compute");
 9        String category = bookStore.get(book2);
10        // print null;
11        System.out.println(category);
12    }
13}

因为 HashMap 是根据对象的散列值确定在 HashMap 中的存储位置的。如果你不重写 hashCode 方法,那么 book1book2JVM 中是两个完全不同的对象,他们的 hashCode 也将会不同,所以在 HashMap 看来他们就不是同一本书。

所以重写完 equals 方法一定要重写 hashCode 方法。写完之后一定要核对是否符合上面提到的 Object.hashCode 通用约定。

大家也可以参考《Effictive Java》这本书,书里面讲的很透彻。

最后还是那句话不要轻易的重写 equals() 方法。

END!

推荐阅读:

Javaer运维指令合集(快餐版)

Java程序员必读核心书单—基础版

Apache httpd 是如何实现高并发服务的

扫描下方二维码,关注公众号,跟着新爷学技术!

新爷的地盘

微信号 : xin-zone

没想到重写Java的equals()方法有这么多讲究

点一下你会更好看耶

没想到重写Java的equals()方法有这么多讲究

原文  http://mp.weixin.qq.com/s?__biz=MzUzMzE4MDY0Nw==&mid=2247483804&idx=1&sn=2fefc994cc789a93d8fe182f754b938c
正文到此结束
Loading...