说Java的equals方法前需要先说说操作符 ==
,因为很多新手都容易困惑,操作符 ==
和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操作符 ==
比较的是对象的内存地址。也就是验证对象的唯一性,只有个两个对象是同一个对象的时候,他们的内存地址才是相同的。但是我们日常开发中,大部分场景是想验证两个对象是否是逻辑相等。这个时候我们可以重写Object的equals()方法。
JAVA的世界里所有的类都继承自Object。所以每个类都包含eqauls()方法。Object中的equals()方法是这样定义的
public boolean equals(Object obj) { return (this == obj); }
从代码可以看出来还是比较的对象内存地址。如何重写eqauls()方法,就变得特别重要,因为一不小心就会造成很严重的错误。还是那句话除非特别必要而且很明确的需要逻辑相等,否则不要重写equals。
重写equals,应当遵守JavaSE的通用约定。
自反性(reflexive) :对于任何非 null
的引用值x, x.equals(x)
必须返回 true
。
对称性(symmetric): 对于任何非 null
的引用值 x
和 y
,当且仅当 x.equals(y)
返回 true
时, y.equals(x)
必须返回 true
。
传递性(transitive): 对于任何非 null
的引用值 x
, y
和 z
,如果 x.equals(y)
返回 true
,并且 y.equals(z)
也返回 true
,那么 x.equals(z)
也必须返回 true
。
一致性(consistent): 对于任何非 null
的引用值x和y, 只要equals的比较操作在对象中所用的信息没有被修改,多次调用 x.equals(y)
就会一致的返回 true
,或者一致的返回 false
。
对于任何非 null
的引用值 x
, x.equals(null)
必须返回 false
。
如果你违反了上面的约定,你的程序也许就会变现的不正常。因为所有的集合类都依赖于传递给他的对象是否遵守equals约定。
根据以上原则我们尝试重写一个自定义的Java类的equals方法。假定我们定一个Book类,希望该类实现逻辑相等。然后重写equals方法。只要书名和出版社一致我们就认为是同一本书。
public class Book { private String name; private String publish; public Book(String name, String publish){ this.name = name; this.publish = publish; } @Override public boolean equals(Object obj){ //使用 == 检查参数是否为这个对象的引用。 if(obj == this) return true; //使用instanceof 检查参数是否为正确类型。 if (!(obj instanceof Book)) return false; //把参数转化成正确类型 Book book = (Book) obj; //对于该类中的关键字段,检查参数中的字段是否与该对象中对应的字段匹配。 return Objects.equals(name,book.name) && Objects.equals(publish,book.publish); } }
这样通过equals方法就可以比较两本书是否是同一本书了,注意我代码中注释部分内容,可以说是重写equals的一些通用技巧。
本来这篇文章写到这里应该结束了,但是说到重写equals就必须提重写hashCode
因为: 重写equals方法一定要重写hashCode方法。
如果不遵守上面这条规则,会给我们的程序带来意想不到的结果。
关于重写hashCode方法, JavaSE 一样给出了约定,如下:
如果只重写了equals方法而没有重写hashCode方法的话,则会违反约定的第二条:相等的对象必须具有相等的散列码 hashCode
两个逻辑相等的对象,如果产生不同的hashCode 将会给HashMap等容器带来意想不到的结果。
举个例子:
public class BookStore { public static void main(String[] args){ HashMap<Book,String> bookStore = new HashMap<Book,String>(); Book book1 = new Book("java","frank publish"); Book book2 = new Book("java","frank publish"); bookStore.put(book1,"compute"); String category = bookStore.get(book2); // print null; System.out.println(category); } }
因为HashMap是根据对象的散列值确定在HashMap中的存储位置的。如果你不重写hashCode方法,那么book1和book2在JVM中是两个完全不同的对象,他们的hashCode也将会不同,所以在HashMap看来他们就不是同一本书。
所以重写完equals方法一定要重写hashCode方法,写完之后要核对是否符合上面提到的Object.hashCode通用约定。大家也可以参考《Effictive Java》这本书,书里面讲的很透彻。
最后还是那句话不要轻易的重写equals()方法。