原创:花括号MC(微信公众号:huakuohao-mc)。关注JAVA基础编程及大数据,注重经验分享及个人成长。
说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 equals() 方法
上面说了Java
操作符==
比较的是对象的内存地址。也就是验证对象的唯一性,只有个两个对象是同一个对象的时候,他们的内存地址才是相同的。
但是我们日常开发中,大部分场景是想验证两个对象是否是逻辑相等。这个时候我们可以重写基类Object
的equals()
方法。
Java
里面所有的类都继承自Object
。所以每个类都包含eqauls()
方法。Object
中的equals()
方法是这样定义的
public boolean equals(Object obj) {
return (this == obj);
}
从代码可以看出来比较的还是对象内存地址。所以如何重写eqauls()
方法,就变得特别重要,因为一不小心就会造成很严重的错误。
还是那句话,除非特别必要而且很明确的需要逻辑相等,否则不要重写equals
方法。
重写equals
重写equals
,应当遵守Java SE
的通用约定。
自反性(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
的一些通用技巧。
hashCode
本来这篇文章写到这里我就想结束了,可是hashCode
说不可以。因为重写equals方法一定要重写hashCode方法。
如果不遵守上面这条规则,会给我们的程序带来意想不到的结果。
关于重写hashCode
方法, JavaSE
一样给出了约定,如下:
- 在应用程序的执行期间,只要对象的
equals
方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次hashCode
方法,它必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,这个整数可以不同。
- 如果两个对象根据
equals(Object)
方法是相等的,那么调用这两个对象中任一个对象的hashCode
方法必须产生同样的整数结果。
- 如果两个对象根据
equals(Object)
方法是不相等的,那么调用这两个对象中任一个对象的hashCode
方法,不要求必须产生不同的整数结果。然而,程序员应该意识到这样的事实,对于不相等的对象产生截然不同的整数结果,有可能提高散列表(hash table
)的性能。
如果只重写了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()
方法。
·END·
Java·大数据·个人成长
微信号:huakuohao-mc