一、前言
本文内容摘自《深入理解Java核心技术:写给Java工程师的干货笔记(基础篇)》一书,2022年出版,作者 张洪亮(@Hollis),阿里巴巴技术专家,著有《Java工程师成神之路》系列文章,《Java工程师成神之路》电子书已开源,可在阿里云开发者社区免费下载。书籍内容比电子书内容要丰富,内容有修改,有需要的读者可以购买正版书籍。
【如何成神:先搬砖,再砌砖,后造砖!】
本文由 @大白有点菜 原创,请勿盗用,转载请说明出处!如果觉得文章还不错,请点点赞,加关注,谢谢!
《Java工程师成神之路》下载地址为:
https://developer.aliyun.com/ebook/395?spm=a2c6h.20345107.ebook-index.24.4c927863j10ats
二、第3章 Java对象【Object类、equals和hashCode的关系】
1、Object类
Java中所有类都默认继承一个类——java.lang.Object类,是所有类的超类。
Object类的方法:
- clone():创建并返回此对象的副本。
- equals(Object obj):指示其它对象是否“等于”此对象。
- hashCode():返回对象的哈希码值。
- notify():唤醒正在等待这个对象的监视器的单个线程。
- notifyAll():唤醒正在等待这个对象的监视器的所有线程。
- wait():使当前线程进入等待状态,直到另外一个线程调用此对象的 notify() 方法或 notifyAll() 方法。
- finalize():当垃圾回收器决定回收某对象时,就会运行该对象的 finalize() 方法。
Object类代码:
package java.lang;
public class Object {
private static native void registerNatives();
static {
registerNatives();
}
public final native Class<?> getClass();
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
protected native Object clone() throws CloneNotSupportedException;
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
public final void wait() throws InterruptedException {
wait(0);
}
protected void finalize() throws Throwable { }
}
2、equals和hashCode的关系
Java 中每个类都隐式地继承自java.lang.Object类,并且会继承equals()和hashcode()两个方法。
在没有重写这两个方法的情况下,equals()的默认实现与“==”操作符一致,即如果引用变量指向同一个对象,则返回true。例如:
public boolean equals(Object obj) {
return (this == obj);
}
【equals()举例(书中例子)】
Person类代码(样例1):
/**
* Person类1
* @author 大白有点菜
*/
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
}
主程序代码(样例1):
/**
* Equals样例1
* @author 大白有点菜
*/
public class EqualsApp1 {
public static void main(String[] args) {
Person person1 = new Person("君莫笑");
Person person2 = new Person("君莫笑");
Person person3 = person1;
System.out.println("person1 == person2 ? " + (person1.equals(person2)));
System.out.println("person1 == person3 ? " + (person1.equals(person3)));
}
}
运行结果(样例1):
person1 == person2 ? false
person1 == person3 ? true
如果想判断两个对象的内容是否相等,则需要重写equals()和hashcode()方法。例如:
Person类代码(样例2):
import java.util.Objects;
/**
* Person类2
* @author 大白有点菜
*/
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Person person = (Person) obj;
return Objects.equals(name, person.name);
}
}
主程序代码(样例2):
/**
* Equals样例2
* @author 大白有点菜
*/
public class EqualsApp2 {
public static void main(String[] args) {
Person person1 = new Person("文西与阿漆");
Person person2 = new Person("文西与阿漆");
Person person3 = person1;
System.out.println("person1 == person2 ? " + (person1.equals(person2)));
System.out.println("person1 == person3 ? " + (person1.equals(person3)));
}
}
运行结果(样例2):
person1 == person2 ? true
person1 == person3 ? true
3、equals的多种写法
(1)第一种:IntellijIDEA默认的实现方式。
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Person person = (Person) obj;
return name != null ? name.equals(person.name) : person.name == null;
}
(2)第二种:基于Apache Commons Lang框架的实现方式。
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Person person = (Person) obj;
return new EqualsBuilder().append(name, person.name).isEquals();
}
(3)第三种:基于Guava框架的实现方式。
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Person person = (Person) obj;
return Objects.equals(name, person.name);
}
4、equals和hashCode
hashCode()方法作用是返回对象的哈希值,返回值类型是int。这个哈希值的作用是确定该对象在哈希表中位置。
Jsohua Bloch在Effective Java中对hashCode有这样的描述:你必须在每个重写equals()的类中重写hashCode()。如果不这么做,那么将违反Object.hashCode()的一般约定,这将阻止类与所有基于散列的集合(包括HashMap、HashSet和Hashtable)一起正常工作。
在HashMap和HashSet等基于散列的集合中,会使用对象的hashCode值来确定该对象应该如何存储在集合中,并且再次使用hashCode来在其集合中定位对象。
如果有一个类,只重写了equals方法,而没有重写hashCode方法,那么会发生什么问题?例如:
Person类代码(样例3):
import java.util.Objects;
/**
* Person类3
* @author 大白有点菜
*/
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Person person = (Person) obj;
return Objects.equals(name, person.name);
}
}
主程序代码(样例3):
/**
* Equals样例3
* @author 大白有点菜
*/
public class EqualsApp3 {
public static void main(String[] args) {
Person person1 = new Person("文西与阿漆");
Person person2 = new Person("文西与阿漆");
HashSet<Person> set = new HashSet<>();
set.add(person1);
set.add(person2);
System.out.println("set.size() = " + set.size());
}
}
运行结果:
set.size() = 2
以上代码定义了一个 Person 类,只重写了equals方法,并没有重写hashCode方法,然后定义两个值内容一样的对象,尝试它们放入一个不能重复的Set,最后输出这个Set的元素个数是2,说明Set认为这两个对象不是相等的。
两个对象相等的严格定义是:对象内容相等(equals()的结果),并且哈希值也要相等(hashCode的结果)。
给Person添加一个hashCode方法,重新执行,看看结果如何。例如:
Person类代码(样例4):
import java.util.Objects;
/**
* Person类4
* @author 大白有点菜
*/
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Person person = (Person) obj;
return Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
主程序代码(样例4):
/**
* Equals样例4
* @author 大白有点菜
*/
public class EqualsApp4 {
public static void main(String[] args) {
Person person1 = new Person("文西与阿漆");
Person person2 = new Person("文西与阿漆");
HashSet<Person> set = new HashSet<>();
set.add(person1);
set.add(person2);
System.out.println("set.size() = " + set.size());
}
}
运行结果(样例4):
set.size() = 1